Merge "Throw if Redis::SERIALIZER_IGBINARY is not defined"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 11 Sep 2019 04:03:14 +0000 (04:03 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 11 Sep 2019 04:03:14 +0000 (04:03 +0000)
437 files changed:
.phpcs.xml
RELEASE-NOTES-1.34
api.php
autoload.php
composer.json
docs/extension.schema.v1.json
docs/extension.schema.v2.json
docs/hooks.txt
img_auth.php
includes/ActorMigration.php
includes/AjaxResponse.php
includes/DefaultSettings.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/Message/TextFormatter.php
includes/MovePage.php
includes/OutputPage.php
includes/PHPVersionCheck.php
includes/PathRouter.php
includes/Rest/EntryPoint.php
includes/Rest/Handler.php
includes/Rest/Handler/HelloHandler.php
includes/Rest/HttpException.php
includes/Rest/ResponseFactory.php
includes/Rest/Router.php
includes/Rest/SimpleHandler.php
includes/Rest/Validator/BodyValidator.php [new file with mode: 0644]
includes/Rest/Validator/NullBodyValidator.php [new file with mode: 0644]
includes/Rest/Validator/ParamValidatorCallbacks.php [new file with mode: 0644]
includes/Rest/Validator/Validator.php [new file with mode: 0644]
includes/Revision.php
includes/Revision/RevisionStore.php
includes/ServiceWiring.php
includes/Setup.php
includes/Title.php
includes/WebRequest.php
includes/actions/InfoAction.php
includes/api/ApiBase.php
includes/api/ApiExpandTemplates.php
includes/api/ApiMain.php
includes/api/ApiModuleManager.php
includes/api/ApiPageSet.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/ApiQueryAllRevisions.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryDeletedRevisions.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryStashImageInfo.php
includes/api/ApiQueryUserContribs.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiTag.php
includes/api/i18n/ar.json
includes/api/i18n/en.json
includes/api/i18n/fr.json
includes/api/i18n/pl.json
includes/api/i18n/qqq.json
includes/api/i18n/ru.json
includes/api/i18n/zh-hant.json
includes/auth/AuthenticationRequest.php
includes/auth/ResetPasswordSecondaryAuthenticationProvider.php
includes/block/DatabaseBlock.php
includes/cache/LinkCache.php
includes/cache/UserCache.php
includes/cache/localisation/LCStoreDB.php
includes/changes/RecentChange.php
includes/changetags/ChangeTags.php
includes/composer/ComposerPhpunitXmlCoverageEdit.php [new file with mode: 0644]
includes/config/ConfigFactory.php
includes/config/ConfigRepository.php
includes/context/RequestContext.php
includes/dao/DBAccessBase.php
includes/debug/logger/ConsoleLogger.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/JobQueueEnqueueUpdate.php
includes/deferred/LinksUpdate.php
includes/deferred/MessageCacheUpdate.php
includes/deferred/SiteStatsUpdate.php
includes/deferred/UserEditCountUpdate.php
includes/diff/DifferenceEngine.php
includes/externalstore/ExternalStoreDB.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/LocalFileDeleteBatch.php
includes/filerepo/file/OldLocalFile.php
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLCheckMatrix.php
includes/import/ImportStreamSource.php
includes/import/ImportStringSource.php
includes/installer/CliInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteInstaller.php
includes/installer/SqliteUpdater.php
includes/installer/WebInstallerOptions.php
includes/installer/i18n/ar.json
includes/installer/i18n/ckb.json
includes/installer/i18n/fr.json
includes/installer/i18n/ia.json
includes/installer/i18n/it.json
includes/installer/i18n/ja.json
includes/installer/i18n/nl.json
includes/installer/i18n/pl.json
includes/installer/i18n/qqq.json
includes/installer/i18n/sh.json
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/Message/ITextFormatter.php
includes/libs/Message/ListParam.php
includes/libs/Message/ListType.php
includes/libs/Message/MessageParam.php
includes/libs/Message/MessageValue.php
includes/libs/Message/ParamType.php
includes/libs/Message/README.md [new file with mode: 0644]
includes/libs/Message/ScalarParam.php [new file with mode: 0644]
includes/libs/Message/TextParam.php [deleted file]
includes/libs/StatusValue.php
includes/libs/filebackend/FSFileBackend.php
includes/libs/filebackend/FileBackendStore.php
includes/libs/filebackend/MemoryFileBackend.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/filebackend/fileophandle/FileBackendStoreOpHandle.php
includes/libs/filebackend/fsfile/FSFile.php
includes/libs/http/MultiHttpClient.php
includes/libs/objectcache/wancache/WANObjectCache.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/lbfactory/LBFactoryMulti.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php
includes/libs/rdbms/lbfactory/LBFactorySingle.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/logging/LogPage.php
includes/logging/ManualLogEntry.php
includes/page/ImagePage.php
includes/page/WikiPage.php
includes/pager/IndexPager.php
includes/parser/PPDPart.php
includes/parser/PPDStack.php
includes/parser/PPDStackElement.php
includes/parser/PPFrame.php
includes/parser/PPFrame_DOM.php
includes/parser/PPTemplateFrame_Hash.php
includes/parser/Preprocessor.php
includes/parser/Preprocessor_Hash.php
includes/preferences/DefaultPreferencesFactory.php
includes/registration/ExtensionRegistry.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderFileModule.php
includes/resourceloader/ResourceLoaderLanguageDataModule.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/resourceloader/ResourceLoaderUserDefaultsModule.php
includes/resourceloader/ResourceLoaderUserOptionsModule.php
includes/resourceloader/ResourceLoaderUserTokensModule.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevisionDeleteUser.php
includes/session/SessionManager.php
includes/session/SessionOverflowException.php [new file with mode: 0644]
includes/specialpage/LoginSignupSpecialPage.php
includes/specialpage/QueryPage.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialAncientPages.php [new file with mode: 0644]
includes/specials/SpecialAncientpages.php [deleted file]
includes/specials/SpecialBlockList.php
includes/specials/SpecialBrokenRedirects.php
includes/specials/SpecialConfirmEmail.php [new file with mode: 0644]
includes/specials/SpecialConfirmemail.php [deleted file]
includes/specials/SpecialContributions.php
includes/specials/SpecialDeadendPages.php [new file with mode: 0644]
includes/specials/SpecialDeadendpages.php [deleted file]
includes/specials/SpecialDeletedContributions.php
includes/specials/SpecialDoubleRedirects.php
includes/specials/SpecialEditTags.php
includes/specials/SpecialEmailInvalidate.php
includes/specials/SpecialExpandTemplates.php
includes/specials/SpecialFewestRevisions.php [new file with mode: 0644]
includes/specials/SpecialFewestrevisions.php [deleted file]
includes/specials/SpecialFileDuplicateSearch.php
includes/specials/SpecialLinkSearch.php
includes/specials/SpecialListDuplicatedFiles.php
includes/specials/SpecialListRedirects.php [new file with mode: 0644]
includes/specials/SpecialListredirects.php [deleted file]
includes/specials/SpecialLog.php
includes/specials/SpecialLonelyPages.php [new file with mode: 0644]
includes/specials/SpecialLonelypages.php [deleted file]
includes/specials/SpecialLongPages.php [new file with mode: 0644]
includes/specials/SpecialLongpages.php [deleted file]
includes/specials/SpecialMIMESearch.php [new file with mode: 0644]
includes/specials/SpecialMIMEsearch.php [deleted file]
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialMostCategories.php [new file with mode: 0644]
includes/specials/SpecialMostInterwikis.php [new file with mode: 0644]
includes/specials/SpecialMostLinked.php [new file with mode: 0644]
includes/specials/SpecialMostLinkedCategories.php [new file with mode: 0644]
includes/specials/SpecialMostLinkedTemplates.php [new file with mode: 0644]
includes/specials/SpecialMostRevisions.php [new file with mode: 0644]
includes/specials/SpecialMostcategories.php [deleted file]
includes/specials/SpecialMostinterwikis.php [deleted file]
includes/specials/SpecialMostlinked.php [deleted file]
includes/specials/SpecialMostlinkedcategories.php [deleted file]
includes/specials/SpecialMostlinkedtemplates.php [deleted file]
includes/specials/SpecialMostrevisions.php [deleted file]
includes/specials/SpecialNewFiles.php [new file with mode: 0644]
includes/specials/SpecialNewimages.php [deleted file]
includes/specials/SpecialPageData.php
includes/specials/SpecialRecentChangesLinked.php
includes/specials/SpecialRedirect.php
includes/specials/SpecialShortPages.php [new file with mode: 0644]
includes/specials/SpecialShortpages.php [deleted file]
includes/specials/SpecialStatistics.php
includes/specials/SpecialUncategorizedCategories.php [new file with mode: 0644]
includes/specials/SpecialUncategorizedImages.php [new file with mode: 0644]
includes/specials/SpecialUncategorizedPages.php [new file with mode: 0644]
includes/specials/SpecialUncategorizedTemplates.php [new file with mode: 0644]
includes/specials/SpecialUncategorizedcategories.php [deleted file]
includes/specials/SpecialUncategorizedimages.php [deleted file]
includes/specials/SpecialUncategorizedpages.php [deleted file]
includes/specials/SpecialUncategorizedtemplates.php [deleted file]
includes/specials/SpecialUndelete.php
includes/specials/SpecialUnusedCategories.php [new file with mode: 0644]
includes/specials/SpecialUnusedImages.php [new file with mode: 0644]
includes/specials/SpecialUnusedTemplates.php [new file with mode: 0644]
includes/specials/SpecialUnusedcategories.php [deleted file]
includes/specials/SpecialUnusedimages.php [deleted file]
includes/specials/SpecialUnusedtemplates.php [deleted file]
includes/specials/SpecialUnwatchedPages.php [new file with mode: 0644]
includes/specials/SpecialUnwatchedpages.php [deleted file]
includes/specials/SpecialWantedCategories.php [new file with mode: 0644]
includes/specials/SpecialWantedTemplates.php [new file with mode: 0644]
includes/specials/SpecialWantedcategories.php [deleted file]
includes/specials/SpecialWantedtemplates.php [deleted file]
includes/specials/SpecialWithoutInterwiki.php [new file with mode: 0644]
includes/specials/SpecialWithoutinterwiki.php [deleted file]
includes/specials/pagers/ActiveUsersPager.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/MergeHistoryPager.php
includes/specials/pagers/NewFilesPager.php
includes/specials/pagers/ProtectedTitlesPager.php
includes/specials/pagers/UsersPager.php
includes/upload/UploadFromStash.php
includes/user/User.php
includes/watcheditem/NoWriteWatchedItemStore.php
includes/widget/CheckMatrixWidget.php
includes/widget/ComplexTitleInputWidget.php
includes/widget/NamespaceInputWidget.php
includes/widget/SelectWithInputWidget.php
includes/widget/SizeFilterWidget.php
includes/widget/TagMultiselectWidget.php
index.php
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/ban.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ckb.json
languages/i18n/co.json
languages/i18n/cs.json
languages/i18n/diq.json
languages/i18n/dtp.json
languages/i18n/en.json
languages/i18n/eu.json
languages/i18n/exif/diq.json
languages/i18n/exif/fr.json
languages/i18n/exif/ia.json
languages/i18n/exif/id.json
languages/i18n/exif/mk.json
languages/i18n/exif/nds-nl.json
languages/i18n/exif/pl.json
languages/i18n/exif/sd.json
languages/i18n/exif/szl.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fit.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gom-deva.json
languages/i18n/gom-latn.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/khw.json
languages/i18n/ko.json
languages/i18n/luz.json
languages/i18n/min.json
languages/i18n/mk.json
languages/i18n/mni.json
languages/i18n/my.json
languages/i18n/nap.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nqo.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/sk.json
languages/i18n/sv.json
languages/i18n/szl.json
languages/i18n/te.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/uk.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesHyw.php
load.php
maintenance/Doxyfile
maintenance/Maintenance.php
maintenance/archives/patch-drop-user-fields.sql [new file with mode: 0644]
maintenance/cleanupImages.php
maintenance/cleanupTitles.php
maintenance/compareParsers.php
maintenance/convertLinks.php
maintenance/dumpIterator.php
maintenance/dumpUploads.php
maintenance/getReplicaServer.php
maintenance/importDump.php
maintenance/importImages.php
maintenance/includes/BackupDumper.php
maintenance/includes/MWDoxygenFilter.php [new file with mode: 0644]
maintenance/includes/MigrateActors.php
maintenance/install.php
maintenance/jsduck/categories.json
maintenance/mergeMessageFileList.php
maintenance/mwdoc-filter.php
maintenance/mwdocgen.php
maintenance/postgres/tables.sql
maintenance/preprocessDump.php
maintenance/preprocessorFuzzTest.php
maintenance/reassignEdits.php
maintenance/rebuildImages.php
maintenance/recountCategories.php
maintenance/removeUnusedAccounts.php
maintenance/renderDump.php
maintenance/resetUserTokens.php
maintenance/sqlite/archives/patch-archive-drop-ar_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-filearchive-drop-fa_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-image-drop-img_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-ipblocks-drop-ipb_by.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-logging-drop-log_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-oldimage-drop-oi_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-recentchanges-drop-rc_user.sql [new file with mode: 0644]
maintenance/storage/blobs.sql
maintenance/storage/checkStorage.php
maintenance/storage/recompressTracked.php
maintenance/tables.sql
maintenance/userOptions.php
opensearch_desc.php
phpunit.xml.dist
profileinfo.php
resources/Resources.php
resources/src/jquery.tablesorter/jquery.tablesorter.js
resources/src/jquery/jquery.accessKeyLabel.js [deleted file]
resources/src/jquery/jquery.highlightText.js
resources/src/mediawiki.RegExp.js [deleted file]
resources/src/mediawiki.Title/Title.js
resources/src/mediawiki.Title/phpCharToUpper.json
resources/src/mediawiki.htmlform.checker.js
resources/src/mediawiki.htmlform/cloner.js
resources/src/mediawiki.inspect.js
resources/src/mediawiki.page.watch.ajax.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.monobook.less [deleted file]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.vector.less [deleted file]
resources/src/mediawiki.rcfilters/ui/MainWrapperWidget.js
resources/src/mediawiki.util.js [deleted file]
resources/src/mediawiki.util/.eslintrc.json [new file with mode: 0644]
resources/src/mediawiki.util/jquery.accessKeyLabel.js [new file with mode: 0644]
resources/src/mediawiki.util/util.js [new file with mode: 0644]
resources/src/mediawiki.widgets.datetime/DateTimeFormatter.js
resources/src/moment/moment-locale-overrides.js
rest.php
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiUnitTestCase.php
tests/phpunit/includes/ActorMigrationTest.php
tests/phpunit/includes/ActorMigrationTest.sql [new file with mode: 0644]
tests/phpunit/includes/Message/TextFormatterTest.php
tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php
tests/phpunit/includes/Rest/EntryPointTest.php
tests/phpunit/includes/Revision/RevisionQueryInfoTest.php
tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/api/ApiDeleteTest.php
tests/phpunit/includes/api/ApiMainTest.php
tests/phpunit/includes/api/ApiModuleManagerTest.php
tests/phpunit/includes/api/ApiPageSetTest.php
tests/phpunit/includes/api/ApiQueryLanguageinfoTest.php
tests/phpunit/includes/api/ApiRevisionDeleteTest.php
tests/phpunit/includes/api/format/ApiFormatBaseTest.php
tests/phpunit/includes/api/format/ApiFormatTestBase.php
tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/filebackend/FileBackendTest.php
tests/phpunit/includes/libs/Message/MessageValueTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
tests/phpunit/includes/logging/DatabaseLogEntryTest.php
tests/phpunit/includes/page/PageArchiveTestBase.php
tests/phpunit/includes/registration/ExtensionRegistryTest.php
tests/phpunit/includes/session/SessionManagerTest.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
tests/phpunit/includes/specials/SpecialMIMESearchTest.php
tests/phpunit/includes/specials/SpecialShortPagesTest.php [new file with mode: 0644]
tests/phpunit/includes/specials/SpecialShortpagesTest.php [deleted file]
tests/phpunit/includes/specials/SpecialUncategorizedCategoriesTest.php [new file with mode: 0644]
tests/phpunit/includes/specials/UncategorizedCategoriesPageTest.php [deleted file]
tests/phpunit/includes/user/UserTest.php
tests/phpunit/maintenance/MWDoxygenFilterTest.php [new file with mode: 0644]
tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php
tests/phpunit/unit/includes/Rest/RouterTest.php
tests/phpunit/unit/includes/installer/SqliteInstallerTest.php [new file with mode: 0644]
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js [deleted file]
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
thumb.php
thumb_handler.php

index 9f11ebc..f971881 100644 (file)
                        Whitelist existing violations, but enable the sniff to prevent
                        any new occurrences.
                -->
-               <exclude-pattern>*/includes/specials/SpecialMostinterwikis\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialAncientpages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialBrokenRedirects\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialConfirmemail\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialDeadendpages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialDeletedContributions\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialDoubleRedirects\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialEmailInvalidate\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialFewestrevisions\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialFileDuplicateSearch\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialLinkSearch\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialListDuplicatedFiles\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialListredirects\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialLonelypages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialLongpages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMIMEsearch\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMediaStatistics\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMostcategories\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialMostimages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMostlinked\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMostlinkedcategories\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMostlinkedtemplates\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialMostrevisions\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialMovepage\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialNewimages\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialRandompage\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialShortpages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUncategorizedcategories\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUncategorizedimages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUncategorizedpages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUncategorizedtemplates\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUnusedcategories\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUnusedimages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUnusedtemplates\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialUnwatchedpages\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialUserrights\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialWantedcategories\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialWantedfiles\.php</exclude-pattern>
                <exclude-pattern>*/includes/specials/SpecialWantedpages\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialWantedtemplates\.php</exclude-pattern>
-               <exclude-pattern>*/includes/specials/SpecialWithoutinterwiki\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/CodeCleanerGlobalsPass.inc</exclude-pattern>
                <exclude-pattern>*/maintenance/archives/upgradeLogging\.php</exclude-pattern>
                <exclude-pattern>*/maintenance/benchmarks/bench_HTTP_HTTPS\.php</exclude-pattern>
index 5cd1bbc..09195e1 100644 (file)
@@ -26,6 +26,13 @@ For notes on 1.33.x and older releases, see HISTORY.
 
 === Configuration changes for system administrators in 1.34 ===
 
+In an effort to enforce best practices for passwords, MediaWiki will now warn
+users, and suggest that they change their password, if it is in the list of
+100,000 commonly used passwords that are considered bad passwords. If you want
+to disable this for your users, please add the following to your local settings:
+
+$wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
+
 ==== New configuration ====
 * $wgAllowExternalReqID (T201409) - This configuration setting controls whether
   Mediawiki accepts the request ID set by the incoming request via the
@@ -66,12 +73,14 @@ For notes on 1.33.x and older releases, see HISTORY.
   which was deprecated in 1.30, no longer works. Instead, $wgProxyList should be
   an array with IP addresses as the values, or a string path to a file
   containing one IP address per line.
+* $wgCookieSetOnAutoblock and $wgCookieSetOnIpBlock are now enabled by default.
 * …
 
 ==== Removed configuration ====
 * $wgWikiDiff2MovedParagraphDetectionCutoff — If you still want a custom change
   size threshold, please specify in php.ini, using the configuration variable
   wikidiff2.moved_paragraph_detection_cutoff.
+* $wgUseESI - This experimental setting, deprecated in 1.33, is now removed.
 * $wgDebugPrintHttpHeaders - The default of including HTTP headers in the
   debug log channel is no longer configurable. The debug log itself remains
   configurable via $wgDebugLogFile.
@@ -82,6 +91,8 @@ For notes on 1.33.x and older releases, see HISTORY.
 * $wgDBOracleDRCP - If you must use persistent connections, set DBO_PERSISTENT
   in the 'flags' field for servers in $wgDBServers (or $wgLBFactoryConf).
 * $wgMemCachedDebug - Set the cache "debug" field in $wgObjectCaches instead.
+* $wgActorTableSchemaMigrationStage has been removed. Extension code for
+  MediaWiki 1.31+ finding it unset should treat it as being SCHEMA_COMPAT_NEW.
 
 === New user-facing features in 1.34 ===
 * Special:Mute has been added as a quick way for users to block unwanted emails
@@ -109,6 +120,12 @@ For notes on 1.33.x and older releases, see HISTORY.
   GetBlockedStatus.
 * ObjectFactory is available as a service. When used as a service, the object
   specs can now specify needed DI services.
+* (T222388) Special pages can now be specified as an ObjectFactory spec,
+  allowing the construction of special pages that require services to be
+  injected in their constructor.
+* (T222388) API modules can now be specified as an ObjectFactory spec,
+  allowing the construction of modules that require services to be injected
+  in their constructor.
 
 === External library changes in 1.34 ===
 
@@ -145,8 +162,15 @@ For notes on 1.33.x and older releases, see HISTORY.
 === Action API changes in 1.34 ===
 * The 'recenteditcount' response property from action=query list=allusers,
   deprecated in 1.25, has been removed.
+* (T60993) action=query list=filearchive, list=alldeletedrevisions and
+  prop=deletedrevisions no longer require the 'deletedhistory' user right.
 
 === Action API internal changes in 1.34 ===
+* The exception thrown in ApiModuleManager::getModule has been changed
+  from an MWException to an UnexpectedValueException, thrown by ObjectFactory.
+  ApiModuleManager::getModule now also throws InvalidArgumentExceptions when
+  ObjectFactory is presented with an invalid spec or incorrectly constructed
+  objects.
 * …
 
 === Languages updated in 1.34 ===
@@ -166,6 +190,34 @@ because of Phabricator reports.
   * CryptRand class
   * CryptRand service
   * Functions of the MWCryptRand class: singleton(), wasStrong() and generate().
+* Various Special Page PHP Classes were renamed (mostly casing changes):
+  * SpecialAncientpages => SpecialAncientPages
+  * SpecialConfirmemail => SpecialConfirmEmail
+  * SpecialDeadendpages => SpecialDeadendPages
+  * SpecialFewestrevisions => SpecialFewestRevisions
+  * SpecialListredirects => SpecialListRedirects
+  * SpecialLonelypages => SpecialLonelyPages
+  * SpecialLongpages => SpecialLongPages
+  * SpecialMIMEsearch => SpecialMIMESearch
+  * SpecialMostcategories => SpecialMostCategories
+  * SpecialMostinterwikis => SpecialMostInterwikis
+  * SpecialMostlinked => SpecialMostLinked
+  * SpecialMostlinkedcategories => SpecialMostLinkedCategories
+  * SpecialMostlinkedtemplates => SpecialMostLinkedTemplates
+  * SpecialMostrevisions => SpecialMostRevisions
+  * SpecialNewimages => SpecialNewFiles
+  * SpecialShortpages => SpecialShortPages
+  * SpecialUncategorizedcategories => SpecialUncategorizedCategories
+  * SpecialUncategorizedimages => SpecialUncategorizedImages
+  * SpecialUncategorizedpages => SpecialUncategorizedPages
+  * SpecialUncategorizedtemplates => SpecialUncategorizedTemplates
+  * SpecialUnusedcategories => SpecialUnusedCategories
+  * SpecialUnusedimages => SpecialUnusedImages
+  * SpecialUnusedtemplates => SpecialUnusedTemplates
+  * SpecialUnwatchedpages => SpecialUnwatchedPages
+  * SpecialWantedcategories => SpecialWantedCategories
+  * SpecialWantedtemplates => SpecialWantedTemplates
+  * SpecialWithoutinterwiki => SpecialWithoutInterwiki
 * Language::setCode, deprecated in 1.32, was removed. Use Language::factory to
   create a new Language object with a different language code.
 * MWNamespace::clearCaches() has been removed.  So has the $rebuild parameter
@@ -361,7 +413,21 @@ because of Phabricator reports.
   initialized after calling SearchResult::initFromTitle().
 * The UserIsBlockedFrom hook is only called if a block is found first, and
   should only be used to unblock a blocked user.
-* …
+* Parameters for index.php from PATH_INFO, such as the title, are no longer
+  written to $_GET.
+* The selectFields() methods on classes LocalFile, ArchivedFile, OldLocalFile,
+  DatabaseBlock, and RecentChange, deprecated in 1.31, have been removed. Use
+  the corresponding getQueryInfo() methods instead.
+* The following methods on Revision, deprecated since 1.31, have been removed.
+  Use RevisionStore::getQueryInfo() or RevisionStore::getArchiveQueryInfo()
+  instead.
+  * Revision::userJoinCond()
+  * Revision::pageJoinCond()
+  * Revision::selectFields()
+  * Revision::selectArchiveFields()
+  * Revision::selectTextFields()
+  * Revision::selectPageFields()
+  * Revision::selectUserFields()
 
 === Deprecations in 1.34 ===
 * The MWNamespace class is deprecated. Use NamespaceInfo.
@@ -417,6 +483,8 @@ because of Phabricator reports.
 * ResourceLoaderContext::getConfig and ResourceLoaderContext::getLogger have
   been deprecated. Inside ResourceLoaderModule subclasses, use the local methods
   instead. Elsewhere, use the methods from the ResourceLoader class.
+* The 'jquery.accessKeyLabel' module has been deprecated. This jQuery
+  plugin is now ships as part of the 'mediawiki.util' module bundle.
 * The Profiler::setTemplated and Profiler::getTemplated methods have been
   deprecated. Use Profiler::setAllowOutput and Profiler::getAllowOutput
   instead.
@@ -479,6 +547,22 @@ because of Phabricator reports.
   class. If you extend this class please be sure to override all its methods
   or extend RevisionSearchResult.
 * Skin::getSkinNameMessages() is deprecated and no longer used.
+* The mediawiki.RegExp module is deprecated; use mw.util.escapeRegExp() instead.
+* Specifying a SpecialPage object for the list of special pages (either through
+  the SpecialPage_initList hook or by adding to $wgSpecialPages) is now
+  deprecated.
+* Use of ActorMigration with 'ar_user', 'img_user', 'oi_user', 'fa_user',
+  'rc_user', 'log_user', and 'ipb_by' is deprecated. Queries should be adjusted
+  to use the corresponding actor fields directly. Note that use with
+  'rev_user' is *not* deprecated at this time.
+* Specifying both the class and factory parameters for
+  ApiModuleManager::addModule is now deprecated. The ObjectFactory spec should
+  be used instead.
+* The UserIsHidden hook is deprecated. Use GetUserBlock instead, and add a
+  system block that hides the user.
+* The GetBlockedStatus hook is deprecated. Use GetUserBlock instead, to add or
+  remove a block.
+* $wgContentHandlerUseDB is deprecated and should always be true.
 
 === Other changes in 1.34 ===
 * …
diff --git a/api.php b/api.php
index 0fb674b..6f4bac3 100644 (file)
--- a/api.php
+++ b/api.php
@@ -31,6 +31,7 @@ use MediaWiki\Logger\LegacyLogger;
 
 // So extensions (and other code) can check whether they're running in API mode
 define( 'MW_API', true );
+define( 'MW_ENTRY_POINT', 'api' );
 
 require __DIR__ . '/includes/WebStart.php';
 
@@ -44,7 +45,7 @@ if ( !$wgRequest->checkUrlExtension() ) {
 // PATH_INFO can be used for stupid things. We don't support it for api.php at
 // all, so error out if it's present.
 if ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) {
-       $correctUrl = wfAppendQuery( wfScript( 'api' ), $wgRequest->getQueryValues() );
+       $correctUrl = wfAppendQuery( wfScript( 'api' ), $wgRequest->getQueryValuesOnly() );
        $correctUrl = wfExpandUrl( $correctUrl, PROTO_CANONICAL );
        header( "Location: $correctUrl", true, 301 );
        echo 'This endpoint does not support "path info", i.e. extra text between "api.php"'
index eb54f7c..f95e001 100644 (file)
@@ -20,7 +20,6 @@ $wgAutoloadLocalClasses = [
        'AllMessagesTablePager' => __DIR__ . '/includes/specials/pagers/AllMessagesTablePager.php',
        'AllTrans' => __DIR__ . '/maintenance/language/alltrans.php',
        'AlphabeticPager' => __DIR__ . '/includes/pager/AlphabeticPager.php',
-       'AncientPagesPage' => __DIR__ . '/includes/specials/SpecialAncientpages.php',
        'AnsiTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
        'ApiAMCreateAccount' => __DIR__ . '/includes/api/ApiAMCreateAccount.php',
        'ApiAuthManagerHelper' => __DIR__ . '/includes/api/ApiAuthManagerHelper.php',
@@ -215,7 +214,6 @@ $wgAutoloadLocalClasses = [
        'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
        'BmpHandler' => __DIR__ . '/includes/media/BmpHandler.php',
        'BotPassword' => __DIR__ . '/includes/user/BotPassword.php',
-       'BrokenRedirectsPage' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php',
        'BufferingStatsdDataFactory' => __DIR__ . '/includes/libs/stats/BufferingStatsdDataFactory.php',
        'CLIParser' => __DIR__ . '/maintenance/parse.php',
        'CSSMin' => __DIR__ . '/includes/libs/CSSMin.php',
@@ -298,6 +296,7 @@ $wgAutoloadLocalClasses = [
        'ComposerJson' => __DIR__ . '/includes/libs/composer/ComposerJson.php',
        'ComposerLock' => __DIR__ . '/includes/libs/composer/ComposerLock.php',
        'ComposerPackageModifier' => __DIR__ . '/includes/composer/ComposerPackageModifier.php',
+       'ComposerPhpunitXmlCoverageEdit' => __DIR__ . '/includes/composer/ComposerPhpunitXmlCoverageEdit.php',
        'ComposerVendorHtaccessCreator' => __DIR__ . '/includes/composer/ComposerVendorHtaccessCreator.php',
        'ComposerVersionNormalizer' => __DIR__ . '/includes/composer/ComposerVersionNormalizer.php',
        'CompressOld' => __DIR__ . '/maintenance/storage/compressOld.php',
@@ -365,7 +364,6 @@ $wgAutoloadLocalClasses = [
        'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
        'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
        'DateFormatterFactory' => __DIR__ . '/includes/parser/DateFormatterFactory.php',
-       'DeadendPagesPage' => __DIR__ . '/includes/specials/SpecialDeadendpages.php',
        'DeduplicateArchiveRevId' => __DIR__ . '/maintenance/deduplicateArchiveRevId.php',
        'DeferrableCallback' => __DIR__ . '/includes/deferred/DeferrableCallback.php',
        'DeferrableUpdate' => __DIR__ . '/includes/deferred/DeferrableUpdate.php',
@@ -387,7 +385,6 @@ $wgAutoloadLocalClasses = [
        'DeletePageJob' => __DIR__ . '/includes/jobqueue/jobs/DeletePageJob.php',
        'DeleteSelfExternals' => __DIR__ . '/maintenance/deleteSelfExternals.php',
        'DeletedContribsPager' => __DIR__ . '/includes/specials/pagers/DeletedContribsPager.php',
-       'DeletedContributionsPage' => __DIR__ . '/includes/specials/SpecialDeletedContributions.php',
        'DependencyWrapper' => __DIR__ . '/includes/cache/dependency/DependencyWrapper.php',
        'DeprecatedGlobal' => __DIR__ . '/includes/DeprecatedGlobal.php',
        'DeprecatedInterfaceFinder' => __DIR__ . '/maintenance/findDeprecated.php',
@@ -413,7 +410,6 @@ $wgAutoloadLocalClasses = [
        'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
        'DnsSrvDiscoverer' => __DIR__ . '/includes/libs/DnsSrvDiscoverer.php',
        'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
-       'DoubleRedirectsPage' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
        'DummyLinker' => __DIR__ . '/includes/DummyLinker.php',
        'DummySearchIndexFieldDefinition' => __DIR__ . '/includes/search/DummySearchIndexFieldDefinition.php',
        'DummyTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
@@ -446,8 +442,6 @@ $wgAutoloadLocalClasses = [
        'EditPage' => __DIR__ . '/includes/EditPage.php',
        'EditWatchlistCheckboxSeriesField' => __DIR__ . '/includes/specials/formfields/EditWatchlistCheckboxSeriesField.php',
        'EditWatchlistNormalHTMLForm' => __DIR__ . '/includes/specials/forms/EditWatchlistNormalHTMLForm.php',
-       'EmailConfirmation' => __DIR__ . '/includes/specials/SpecialConfirmemail.php',
-       'EmailInvalidation' => __DIR__ . '/includes/specials/SpecialEmailInvalidate.php',
        'EmailNotification' => __DIR__ . '/includes/mail/EmailNotification.php',
        'EmaillingJob' => __DIR__ . '/includes/jobqueue/jobs/EmaillingJob.php',
        'EmptyBagOStuff' => __DIR__ . '/includes/libs/objectcache/EmptyBagOStuff.php',
@@ -504,7 +498,6 @@ $wgAutoloadLocalClasses = [
        'FeedItem' => __DIR__ . '/includes/changes/FeedItem.php',
        'FeedUtils' => __DIR__ . '/includes/FeedUtils.php',
        'FetchText' => __DIR__ . '/maintenance/fetchText.php',
-       'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php',
        'Field' => __DIR__ . '/includes/libs/rdbms/field/Field.php',
        'File' => __DIR__ . '/includes/filerepo/file/File.php',
        'FileAwareNodeVisitor' => __DIR__ . '/maintenance/findDeprecated.php',
@@ -524,7 +517,6 @@ $wgAutoloadLocalClasses = [
        'FileContentsHasher' => __DIR__ . '/includes/utils/FileContentsHasher.php',
        'FileDeleteForm' => __DIR__ . '/includes/FileDeleteForm.php',
        'FileDependency' => __DIR__ . '/includes/cache/dependency/FileDependency.php',
-       'FileDuplicateSearchPage' => __DIR__ . '/includes/specials/SpecialFileDuplicateSearch.php',
        'FileJournal' => __DIR__ . '/includes/libs/filebackend/filejournal/FileJournal.php',
        'FileOp' => __DIR__ . '/includes/libs/filebackend/fileop/FileOp.php',
        'FileOpBatch' => __DIR__ . '/includes/libs/filebackend/FileOpBatch.php',
@@ -784,14 +776,11 @@ $wgAutoloadLocalClasses = [
        'LinkCache' => __DIR__ . '/includes/cache/LinkCache.php',
        'LinkFilter' => __DIR__ . '/includes/LinkFilter.php',
        'LinkHolderArray' => __DIR__ . '/includes/parser/LinkHolderArray.php',
-       'LinkSearchPage' => __DIR__ . '/includes/specials/SpecialLinkSearch.php',
        'Linker' => __DIR__ . '/includes/Linker.php',
        'LinksDeletionUpdate' => __DIR__ . '/includes/deferred/LinksDeletionUpdate.php',
        'LinksUpdate' => __DIR__ . '/includes/deferred/LinksUpdate.php',
-       'ListDuplicatedFilesPage' => __DIR__ . '/includes/specials/SpecialListDuplicatedFiles.php',
        'ListToggle' => __DIR__ . '/includes/ListToggle.php',
        'ListVariants' => __DIR__ . '/maintenance/language/listVariants.php',
-       'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php',
        'LoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancer.php',
        'LoadBalancerSingle' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php',
        'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
@@ -817,9 +806,6 @@ $wgAutoloadLocalClasses = [
        'LoggedUpdateMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
        'LoginHelper' => __DIR__ . '/includes/specials/helpers/LoginHelper.php',
        'LoginSignupSpecialPage' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
-       'LonelyPagesPage' => __DIR__ . '/includes/specials/SpecialLonelypages.php',
-       'LongPagesPage' => __DIR__ . '/includes/specials/SpecialLongpages.php',
-       'MIMEsearchPage' => __DIR__ . '/includes/specials/SpecialMIMEsearch.php',
        'MSCompoundFileReader' => __DIR__ . '/includes/libs/mime/MSCompoundFileReader.php',
        'MWCallableUpdate' => __DIR__ . '/includes/deferred/MWCallableUpdate.php',
        'MWCallbackStream' => __DIR__ . '/includes/http/MWCallbackStream.php',
@@ -829,6 +815,7 @@ $wgAutoloadLocalClasses = [
        'MWCryptRand' => __DIR__ . '/includes/utils/MWCryptRand.php',
        'MWDebug' => __DIR__ . '/includes/debug/MWDebug.php',
        'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
+       'MWDoxygenFilter' => __DIR__ . '/maintenance/includes/MWDoxygenFilter.php',
        'MWException' => __DIR__ . '/includes/exception/MWException.php',
        'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
        'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
@@ -865,7 +852,6 @@ $wgAutoloadLocalClasses = [
        'McrUndoAction' => __DIR__ . '/includes/actions/McrUndoAction.php',
        'MediaHandler' => __DIR__ . '/includes/media/MediaHandler.php',
        'MediaHandlerFactory' => __DIR__ . '/includes/media/MediaHandlerFactory.php',
-       'MediaStatisticsPage' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php',
        'MediaTransformError' => __DIR__ . '/includes/media/MediaTransformError.php',
        'MediaTransformInvalidParametersException' => __DIR__ . '/includes/media/MediaTransformInvalidParametersException.php',
        'MediaTransformOutput' => __DIR__ . '/includes/media/MediaTransformOutput.php',
@@ -1000,13 +986,7 @@ $wgAutoloadLocalClasses = [
        'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
        'MimeAnalyzer' => __DIR__ . '/includes/libs/mime/MimeAnalyzer.php',
        'MinifyScript' => __DIR__ . '/maintenance/minify.php',
-       'MostcategoriesPage' => __DIR__ . '/includes/specials/SpecialMostcategories.php',
        'MostimagesPage' => __DIR__ . '/includes/specials/SpecialMostimages.php',
-       'MostinterwikisPage' => __DIR__ . '/includes/specials/SpecialMostinterwikis.php',
-       'MostlinkedCategoriesPage' => __DIR__ . '/includes/specials/SpecialMostlinkedcategories.php',
-       'MostlinkedPage' => __DIR__ . '/includes/specials/SpecialMostlinked.php',
-       'MostlinkedTemplatesPage' => __DIR__ . '/includes/specials/SpecialMostlinkedtemplates.php',
-       'MostrevisionsPage' => __DIR__ . '/includes/specials/SpecialMostrevisions.php',
        'MoveBatch' => __DIR__ . '/maintenance/moveBatch.php',
        'MoveFileOp' => __DIR__ . '/includes/libs/filebackend/fileop/MoveFileOp.php',
        'MoveLogFormatter' => __DIR__ . '/includes/logging/MoveLogFormatter.php',
@@ -1329,7 +1309,6 @@ $wgAutoloadLocalClasses = [
        'SerializedValueContainer' => __DIR__ . '/includes/libs/objectcache/serialized/SerializedValueContainer.php',
        'SevenZipStream' => __DIR__ . '/maintenance/includes/SevenZipStream.php',
        'ShiConverter' => __DIR__ . '/languages/classes/LanguageShi.php',
-       'ShortPagesPage' => __DIR__ . '/includes/specials/SpecialShortpages.php',
        'ShowJobs' => __DIR__ . '/maintenance/showJobs.php',
        'ShowSiteStats' => __DIR__ . '/maintenance/showSiteStats.php',
        'Site' => __DIR__ . '/includes/site/Site.php',
@@ -1356,6 +1335,7 @@ $wgAutoloadLocalClasses = [
        'SpecialAllMessages' => __DIR__ . '/includes/specials/SpecialAllMessages.php',
        'SpecialAllMyUploads' => __DIR__ . '/includes/specials/redirects/SpecialAllMyUploads.php',
        'SpecialAllPages' => __DIR__ . '/includes/specials/SpecialAllPages.php',
+       'SpecialAncientPages' => __DIR__ . '/includes/specials/SpecialAncientPages.php',
        'SpecialApiHelp' => __DIR__ . '/includes/specials/SpecialApiHelp.php',
        'SpecialApiSandbox' => __DIR__ . '/includes/specials/SpecialApiSandbox.php',
        'SpecialAutoblockList' => __DIR__ . '/includes/specials/SpecialAutoblockList.php',
@@ -1364,6 +1344,7 @@ $wgAutoloadLocalClasses = [
        'SpecialBlockList' => __DIR__ . '/includes/specials/SpecialBlockList.php',
        'SpecialBookSources' => __DIR__ . '/includes/specials/SpecialBookSources.php',
        'SpecialBotPasswords' => __DIR__ . '/includes/specials/SpecialBotPasswords.php',
+       'SpecialBrokenRedirects' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php',
        'SpecialCachedPage' => __DIR__ . '/includes/specials/SpecialCachedPage.php',
        'SpecialCategories' => __DIR__ . '/includes/specials/SpecialCategories.php',
        'SpecialChangeContentModel' => __DIR__ . '/includes/specials/SpecialChangeContentModel.php',
@@ -1371,35 +1352,55 @@ $wgAutoloadLocalClasses = [
        'SpecialChangeEmail' => __DIR__ . '/includes/specials/SpecialChangeEmail.php',
        'SpecialChangePassword' => __DIR__ . '/includes/specials/SpecialChangePassword.php',
        'SpecialComparePages' => __DIR__ . '/includes/specials/SpecialComparePages.php',
+       'SpecialConfirmEmail' => __DIR__ . '/includes/specials/SpecialConfirmEmail.php',
        'SpecialContributions' => __DIR__ . '/includes/specials/SpecialContributions.php',
        'SpecialCreateAccount' => __DIR__ . '/includes/specials/SpecialCreateAccount.php',
+       'SpecialDeadendPages' => __DIR__ . '/includes/specials/SpecialDeadendPages.php',
+       'SpecialDeletedContributions' => __DIR__ . '/includes/specials/SpecialDeletedContributions.php',
        'SpecialDiff' => __DIR__ . '/includes/specials/SpecialDiff.php',
+       'SpecialDoubleRedirects' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
        'SpecialEditTags' => __DIR__ . '/includes/specials/SpecialEditTags.php',
        'SpecialEditWatchlist' => __DIR__ . '/includes/specials/SpecialEditWatchlist.php',
+       'SpecialEmailInvalidate' => __DIR__ . '/includes/specials/SpecialEmailInvalidate.php',
        'SpecialEmailUser' => __DIR__ . '/includes/specials/SpecialEmailUser.php',
        'SpecialExpandTemplates' => __DIR__ . '/includes/specials/SpecialExpandTemplates.php',
        'SpecialExport' => __DIR__ . '/includes/specials/SpecialExport.php',
+       'SpecialFewestRevisions' => __DIR__ . '/includes/specials/SpecialFewestRevisions.php',
+       'SpecialFileDuplicateSearch' => __DIR__ . '/includes/specials/SpecialFileDuplicateSearch.php',
        'SpecialFilepath' => __DIR__ . '/includes/specials/SpecialFilepath.php',
        'SpecialGoToInterwiki' => __DIR__ . '/includes/specials/SpecialGoToInterwiki.php',
        'SpecialImport' => __DIR__ . '/includes/specials/SpecialImport.php',
        'SpecialJavaScriptTest' => __DIR__ . '/includes/specials/SpecialJavaScriptTest.php',
        'SpecialLinkAccounts' => __DIR__ . '/includes/specials/SpecialLinkAccounts.php',
+       'SpecialLinkSearch' => __DIR__ . '/includes/specials/SpecialLinkSearch.php',
        'SpecialListAdmins' => __DIR__ . '/includes/specials/redirects/SpecialListAdmins.php',
        'SpecialListBots' => __DIR__ . '/includes/specials/redirects/SpecialListBots.php',
+       'SpecialListDuplicatedFiles' => __DIR__ . '/includes/specials/SpecialListDuplicatedFiles.php',
        'SpecialListFiles' => __DIR__ . '/includes/specials/SpecialListFiles.php',
        'SpecialListGrants' => __DIR__ . '/includes/specials/SpecialListGrants.php',
        'SpecialListGroupRights' => __DIR__ . '/includes/specials/SpecialListGroupRights.php',
+       'SpecialListRedirects' => __DIR__ . '/includes/specials/SpecialListRedirects.php',
        'SpecialListUsers' => __DIR__ . '/includes/specials/SpecialListUsers.php',
        'SpecialLockdb' => __DIR__ . '/includes/specials/SpecialLockdb.php',
        'SpecialLog' => __DIR__ . '/includes/specials/SpecialLog.php',
+       'SpecialLonelyPages' => __DIR__ . '/includes/specials/SpecialLonelyPages.php',
+       'SpecialLongPages' => __DIR__ . '/includes/specials/SpecialLongPages.php',
+       'SpecialMIMESearch' => __DIR__ . '/includes/specials/SpecialMIMESearch.php',
+       'SpecialMediaStatistics' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php',
        'SpecialMergeHistory' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
+       'SpecialMostCategories' => __DIR__ . '/includes/specials/SpecialMostCategories.php',
+       'SpecialMostInterwikis' => __DIR__ . '/includes/specials/SpecialMostInterwikis.php',
+       'SpecialMostLinked' => __DIR__ . '/includes/specials/SpecialMostLinked.php',
+       'SpecialMostLinkedCategories' => __DIR__ . '/includes/specials/SpecialMostLinkedCategories.php',
+       'SpecialMostLinkedTemplates' => __DIR__ . '/includes/specials/SpecialMostLinkedTemplates.php',
+       'SpecialMostRevisions' => __DIR__ . '/includes/specials/SpecialMostRevisions.php',
        'SpecialMute' => __DIR__ . '/includes/specials/SpecialMute.php',
        'SpecialMyLanguage' => __DIR__ . '/includes/specials/SpecialMyLanguage.php',
        'SpecialMycontributions' => __DIR__ . '/includes/specials/redirects/SpecialMycontributions.php',
        'SpecialMypage' => __DIR__ . '/includes/specials/redirects/SpecialMypage.php',
        'SpecialMytalk' => __DIR__ . '/includes/specials/redirects/SpecialMytalk.php',
        'SpecialMyuploads' => __DIR__ . '/includes/specials/redirects/SpecialMyuploads.php',
-       'SpecialNewFiles' => __DIR__ . '/includes/specials/SpecialNewimages.php',
+       'SpecialNewFiles' => __DIR__ . '/includes/specials/SpecialNewFiles.php',
        'SpecialNewSection' => __DIR__ . '/includes/specials/SpecialNewSection.php',
        'SpecialNewpages' => __DIR__ . '/includes/specials/SpecialNewpages.php',
        'SpecialPage' => __DIR__ . '/includes/specialpage/SpecialPage.php',
@@ -1427,22 +1428,34 @@ $wgAutoloadLocalClasses = [
        'SpecialRevisionDelete' => __DIR__ . '/includes/specials/SpecialRevisionDelete.php',
        'SpecialRunJobs' => __DIR__ . '/includes/specials/SpecialRunJobs.php',
        'SpecialSearch' => __DIR__ . '/includes/specials/SpecialSearch.php',
+       'SpecialShortPages' => __DIR__ . '/includes/specials/SpecialShortPages.php',
        'SpecialSpecialpages' => __DIR__ . '/includes/specials/SpecialSpecialpages.php',
        'SpecialStatistics' => __DIR__ . '/includes/specials/SpecialStatistics.php',
        'SpecialTags' => __DIR__ . '/includes/specials/SpecialTags.php',
        'SpecialTrackingCategories' => __DIR__ . '/includes/specials/SpecialTrackingCategories.php',
        'SpecialUnblock' => __DIR__ . '/includes/specials/SpecialUnblock.php',
+       'SpecialUncategorizedCategories' => __DIR__ . '/includes/specials/SpecialUncategorizedCategories.php',
+       'SpecialUncategorizedImages' => __DIR__ . '/includes/specials/SpecialUncategorizedImages.php',
+       'SpecialUncategorizedPages' => __DIR__ . '/includes/specials/SpecialUncategorizedPages.php',
+       'SpecialUncategorizedTemplates' => __DIR__ . '/includes/specials/SpecialUncategorizedTemplates.php',
        'SpecialUndelete' => __DIR__ . '/includes/specials/SpecialUndelete.php',
        'SpecialUnlinkAccounts' => __DIR__ . '/includes/specials/SpecialUnlinkAccounts.php',
        'SpecialUnlockdb' => __DIR__ . '/includes/specials/SpecialUnlockdb.php',
+       'SpecialUnusedCategories' => __DIR__ . '/includes/specials/SpecialUnusedCategories.php',
+       'SpecialUnusedImages' => __DIR__ . '/includes/specials/SpecialUnusedImages.php',
+       'SpecialUnusedTemplates' => __DIR__ . '/includes/specials/SpecialUnusedTemplates.php',
+       'SpecialUnwatchedPages' => __DIR__ . '/includes/specials/SpecialUnwatchedPages.php',
        'SpecialUpload' => __DIR__ . '/includes/specials/SpecialUpload.php',
        'SpecialUploadStash' => __DIR__ . '/includes/specials/SpecialUploadStash.php',
        'SpecialUploadStashTooLargeException' => __DIR__ . '/includes/specials/exception/SpecialUploadStashTooLargeException.php',
        'SpecialUserLogin' => __DIR__ . '/includes/specials/SpecialUserLogin.php',
        'SpecialUserLogout' => __DIR__ . '/includes/specials/SpecialUserLogout.php',
        'SpecialVersion' => __DIR__ . '/includes/specials/SpecialVersion.php',
+       'SpecialWantedCategories' => __DIR__ . '/includes/specials/SpecialWantedCategories.php',
+       'SpecialWantedTemplates' => __DIR__ . '/includes/specials/SpecialWantedTemplates.php',
        'SpecialWatchlist' => __DIR__ . '/includes/specials/SpecialWatchlist.php',
        'SpecialWhatLinksHere' => __DIR__ . '/includes/specials/SpecialWhatLinksHere.php',
+       'SpecialWithoutInterwiki' => __DIR__ . '/includes/specials/SpecialWithoutInterwiki.php',
        'SqlBagOStuff' => __DIR__ . '/includes/objectcache/SqlBagOStuff.php',
        'SqlSearchResult' => __DIR__ . '/includes/search/SqlSearchResult.php',
        'SqlSearchResultSet' => __DIR__ . '/includes/search/SqlSearchResultSet.php',
@@ -1515,10 +1528,6 @@ $wgAutoloadLocalClasses = [
        'UDPTransport' => __DIR__ . '/includes/libs/UDPTransport.php',
        'UIDGenerator' => __DIR__ . '/includes/utils/UIDGenerator.php',
        'UcdXmlReader' => __DIR__ . '/maintenance/language/generateCollationData.php',
-       'UncategorizedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedcategories.php',
-       'UncategorizedImagesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedimages.php',
-       'UncategorizedPagesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedpages.php',
-       'UncategorizedTemplatesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedtemplates.php',
        'Undelete' => __DIR__ . '/maintenance/undelete.php',
        'UnifiedDiffFormatter' => __DIR__ . '/includes/diff/UnifiedDiffFormatter.php',
        'UnknownContent' => __DIR__ . '/includes/content/UnknownContent.php',
@@ -1527,11 +1536,7 @@ $wgAutoloadLocalClasses = [
        'UnprotectAction' => __DIR__ . '/includes/actions/UnprotectAction.php',
        'UnregisteredLocalFile' => __DIR__ . '/includes/filerepo/file/UnregisteredLocalFile.php',
        'UnsupportedSlotDiffRenderer' => __DIR__ . '/includes/diff/UnsupportedSlotDiffRenderer.php',
-       'UnusedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUnusedcategories.php',
-       'UnusedimagesPage' => __DIR__ . '/includes/specials/SpecialUnusedimages.php',
-       'UnusedtemplatesPage' => __DIR__ . '/includes/specials/SpecialUnusedtemplates.php',
        'UnwatchAction' => __DIR__ . '/includes/actions/UnwatchAction.php',
-       'UnwatchedpagesPage' => __DIR__ . '/includes/specials/SpecialUnwatchedpages.php',
        'UpdateArticleCount' => __DIR__ . '/maintenance/updateArticleCount.php',
        'UpdateCollation' => __DIR__ . '/maintenance/updateCollation.php',
        'UpdateDoubleWidthSearch' => __DIR__ . '/maintenance/updateDoubleWidthSearch.php',
@@ -1595,11 +1600,9 @@ $wgAutoloadLocalClasses = [
        'WANCacheReapUpdate' => __DIR__ . '/includes/deferred/WANCacheReapUpdate.php',
        'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCache.php',
        'WANObjectCacheReaper' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCacheReaper.php',
-       'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php',
        'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php',
        'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php',
        'WantedQueryPage' => __DIR__ . '/includes/specialpage/WantedQueryPage.php',
-       'WantedTemplatesPage' => __DIR__ . '/includes/specials/SpecialWantedtemplates.php',
        'WatchAction' => __DIR__ . '/includes/actions/WatchAction.php',
        'WatchedItem' => __DIR__ . '/includes/watcheditem/WatchedItem.php',
        'WatchedItemQueryService' => __DIR__ . '/includes/watcheditem/WatchedItemQueryService.php',
@@ -1703,7 +1706,6 @@ $wgAutoloadLocalClasses = [
        'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
        'WikitextLogFormatter' => __DIR__ . '/includes/logging/WikitextLogFormatter.php',
        'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
-       'WithoutInterwikiPage' => __DIR__ . '/includes/specials/SpecialWithoutinterwiki.php',
        'WordLevelDiff' => __DIR__ . '/includes/diff/WordLevelDiff.php',
        'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
        'XCFHandler' => __DIR__ . '/includes/media/XCFHandler.php',
index ac89d71..c1f9037 100644 (file)
@@ -97,7 +97,8 @@
        "autoload": {
                "psr-0": {
                        "ComposerHookHandler": "includes/composer",
-                       "ComposerVendorHtaccessCreator": "includes/composer"
+                       "ComposerVendorHtaccessCreator": "includes/composer",
+                       "ComposerPhpunitXmlCoverageEdit":"includes/composer"
                }
        },
        "autoload-dev": {
                "phpunit": "phpunit",
                "phpunit:unit": "phpunit --colors=always --testsuite=core:unit,extensions:unit,skins:unit",
                "phpunit:integration": "phpunit --colors=always --testsuite=core:integration,extensions:integration,skins:integration",
-               "phpunit:coverage": "phpunit --testsuite=core:unit --exclude-group Dump,Broken"
+               "phpunit:coverage": "phpunit --testsuite=core:unit --exclude-group Dump,Broken",
+               "phpunit:coverage-edit": "ComposerPhpunitXmlCoverageEdit::onEvent"
        },
        "config": {
                "optimize-autoloader": true,
index 9ce016f..06701cd 100644 (file)
                },
                "SpecialPages": {
                        "type": "object",
-                       "description": "SpecialPages implemented in this extension (mapping of page name to class name)"
+                       "description": "SpecialPages implemented in this extension (mapping of page name to class name or to ObjectFactory spec)"
                },
                "AutoloadNamespaces": {
                        "type": "object",
index 9d874f4..56d274b 100644 (file)
                },
                "SpecialPages": {
                        "type": "object",
-                       "description": "SpecialPages implemented in this extension (mapping of page name to class name)"
+                       "description": "SpecialPages implemented in this extension (mapping of page name to class name or to ObjectFactory spec)"
                },
                "AutoloadNamespaces": {
                        "type": "object",
index b7ea02c..43bfd8d 100644 (file)
@@ -1585,7 +1585,8 @@ entitled to be in.
 $user: user to promote.
 &$promote: groups that will be added.
 
-'GetBlockedStatus': after loading blocking status of an user from the database
+'GetBlockedStatus': DEPRECATED since 1.34 - use GetUserBlock instead. After
+loading blocking status of a user from the database
 &$user: user (object) being checked
 
 'GetCacheVaryCookies': Get cookies that should vary cache options.
@@ -2827,6 +2828,7 @@ or request state must be added through MakeGlobalVariablesScript instead.
 Skin is made available for skin specific config.
 &$vars: [ variable name => value ]
 $skin: Skin
+$config: Config object (since 1.34)
 
 'ResourceLoaderJqueryMsgModuleMagicWords': Called in
 ResourceLoaderJqueryMsgModule to allow adding magic words for jQueryMsg.
@@ -3728,7 +3730,9 @@ $ip: User's IP address
 false if a UserGetRights hook might remove the named right.
 $right: The user right being checked
 
-'UserIsHidden': Check if the user's name should be hidden. See User::isHidden().
+'UserIsHidden': DEPRECATED since 1.34 - use GetUserBlock instead, and add a
+system block that hides the user. Check if the user's name should be hidden.
+See User::isHidden().
 $user: User in question.
 &$hidden: Set true if the user's name should be hidden.
 
index 6e45e4e..f23de4f 100644 (file)
@@ -39,6 +39,7 @@
  */
 
 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
+define( 'MW_ENTRY_POINT', 'img_auth' );
 require __DIR__ . '/includes/WebStart.php';
 
 # Set action base paths so that WebRequest::getPathInfo()
index 5dde8a0..c79074d 100644 (file)
@@ -28,15 +28,18 @@ use Wikimedia\Rdbms\IDatabase;
  * This class handles the logic for the actor table migration.
  *
  * This is not intended to be a long-term part of MediaWiki; it will be
- * deprecated and removed along with $wgActorTableSchemaMigrationStage.
+ * deprecated and removed once actor migration is complete.
  *
  * @since 1.31
+ * @since 1.34 Use with 'ar_user', 'img_user', 'oi_user', 'fa_user',
+ *  'rc_user', 'log_user', and 'ipb_by' is deprecated. Callers should
+ *  reference the corresponding actor fields directly.
  */
 class ActorMigration {
 
        /**
         * Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage
-        * expects MIGRATION_* or SCHEMA_COMPAT_*
+        * (in MW <1.34) expects MIGRATION_* or SCHEMA_COMPAT_*
         */
        const MIGRATION_STAGE_SCHEMA_COMPAT = 1;
 
@@ -68,6 +71,28 @@ class ActorMigration {
         */
        private static $formerTempTables = [];
 
+       /**
+        * Define fields that are deprecated for use with this class.
+        * @var (string|null)[] Keys are '$key', value is null for soft deprecation
+        *  or a string naming the deprecated version for hard deprecation.
+        */
+       private static $deprecated = [
+               'ar_user' => null, // 1.34
+               'img_user' => null, // 1.34
+               'oi_user' => null, // 1.34
+               'fa_user' => null, // 1.34
+               'rc_user' => null, // 1.34
+               'log_user' => null, // 1.34
+               'ipb_by' => null, // 1.34
+       ];
+
+       /**
+        * Define fields that are removed for use with this class.
+        * @var string[] Keys are '$key', value is the MediaWiki version in which
+        *  use was removed.
+        */
+       private static $removed = [];
+
        /**
         * Define fields that use non-standard mapping
         * @var array Keys are the user id column name, values are arrays with two
@@ -112,6 +137,21 @@ class ActorMigration {
                return MediaWikiServices::getInstance()->getActorMigration();
        }
 
+       /**
+        * Issue deprecation warning/error as appropriate.
+        * @param string $key
+        */
+       private static function checkDeprecation( $key ) {
+               if ( isset( self::$removed[$key] ) ) {
+                       throw new InvalidArgumentException(
+                               "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
+                       );
+               }
+               if ( !empty( self::$deprecated[$key] ) ) {
+                       wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
+               }
+       }
+
        /**
         * Return an SQL condition to test if a user field is anonymous
         * @param string $field Field name or SQL fragment
@@ -152,6 +192,8 @@ class ActorMigration {
         * @phan-return array{tables:string[],fields:string[],joins:array}
         */
        public function getJoin( $key ) {
+               self::checkDeprecation( $key );
+
                if ( !isset( $this->joinCache[$key] ) ) {
                        $tables = [];
                        $fields = [];
@@ -203,6 +245,8 @@ class ActorMigration {
         * @return array to merge into `$values` to `IDatabase->update()` or `$a` to `IDatabase->insert()`
         */
        public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
+               self::checkDeprecation( $key );
+
                if ( isset( self::$tempTables[$key] ) ) {
                        throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
                }
@@ -236,6 +280,8 @@ class ActorMigration {
         *    and extra fields needed for the temp table.
         */
        public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
+               self::checkDeprecation( $key );
+
                if ( isset( self::$formerTempTables[$key] ) ) {
                        wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
                } elseif ( !isset( self::$tempTables[$key] ) ) {
@@ -319,6 +365,8 @@ class ActorMigration {
         *  All tables and joins are aliased, so `+` is safe to use.
         */
        public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
+               self::checkDeprecation( $key );
+
                $tables = [];
                $conds = [];
                $joins = [];
index 323c5d3..0664652 100644 (file)
@@ -182,16 +182,7 @@ class AjaxResponse {
                        if ( $this->mConfig->get( 'UseCdn' ) ) {
                                # Expect explicit purge of the proxy cache, but require end user agents
                                # to revalidate against the proxy on each visit.
-                               # Surrogate-Control controls our CDN, Cache-Control downstream caches
-
-                               if ( $this->mConfig->get( 'UseESI' ) ) {
-                                       wfDeprecated( '$wgUseESI = true', '1.33' );
-                                       header( 'Surrogate-Control: max-age=' . $this->mCacheDuration . ', content="ESI/1.0"' );
-                                       header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
-                               } else {
-                                       header( 'Cache-Control: s-maxage=' . $this->mCacheDuration . ', must-revalidate, max-age=0' );
-                               }
-
+                               header( 'Cache-Control: s-maxage=' . $this->mCacheDuration . ', must-revalidate, max-age=0' );
                        } else {
                                # Let the client do the caching. Cache is not purged.
                                header( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT" );
index 81de1a0..47fd073 100644 (file)
@@ -2748,12 +2748,6 @@ $wgExtensionInfoMTime = false;
  */
 $wgUseCdn = false;
 
-/**
- * If you run Squid3 with ESI support, enable this (default:false):
- * @deprecated in 1.33. This was a now-defunct experimental feature.
- */
-$wgUseESI = false;
-
 /**
  * Add X-Forwarded-Proto to the Vary and Key headers for API requests and
  * RSS/Atom feeds. Use this if you have an SSL termination setup
@@ -4463,7 +4457,7 @@ $wgCentralIdLookupProvider = 'local';
  *             Deprecated since 1.33. Use PasswordNotInLargeBlacklist instead.
  *     - PasswordNotInLargeBlacklist - Password not in best practices list of
  *             100,000 commonly used passwords. Due to the size of the list this
- *      is a probabilistic test.
+ *             is a probabilistic test.
  *
  * If you add custom checks, for Special:PasswordPolicies to display them correctly,
  * every check should have a corresponding passwordpolicies-policy-<check> message,
@@ -4481,28 +4475,25 @@ $wgPasswordPolicy = [
                'bureaucrat' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'sysop' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'interface-admin' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'bot' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'default' => [
                        'MinimalPasswordLength' => [ 'value' => 1, 'suggestChangeOnLogin' => true ],
                        'PasswordCannotMatchUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
                        'PasswordCannotMatchBlacklist' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
                        'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true ],
+                       'PasswordNotInLargeBlacklist' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
                ],
        ],
        'checks' => [
@@ -4897,6 +4888,7 @@ $wgDefaultUserOptions = [
        'wllimit' => 250,
        'useeditwarning' => 1,
        'prefershttps' => 1,
+       'requireemail' => 0,
 ];
 
 /**
@@ -4966,6 +4958,15 @@ $wgSessionProviders = [
        ],
 ];
 
+/**
+ * Temporary feature flag that controls whether users will see a checkbox allowing them to
+ * require providing email during password resets.
+ *
+ * @deprecated This feature is under development, don't assume this flag's existence or function
+ *     outside of MediaWiki.
+ */
+$wgAllowRequiringEmailForResets = false;
+
 /** @} */ # end user accounts }
 
 /************************************************************************//**
@@ -6071,7 +6072,7 @@ $wgSessionName = false;
  * which case there is a possibility of an attacker discovering the names of revdeleted users, so
  * it is best to use this in conjunction with $wgSecretKey being set).
  */
-$wgCookieSetOnAutoblock = false;
+$wgCookieSetOnAutoblock = true;
 
 /**
  * Whether to set a cookie when a logged-out user is blocked. Doing so means that a blocked user,
@@ -6080,7 +6081,7 @@ $wgCookieSetOnAutoblock = false;
  * case there is a possibility of an attacker discovering the names of revdeleted users, so it
  * is best to use this in conjunction with $wgSecretKey being set).
  */
-$wgCookieSetOnIpBlock = false;
+$wgCookieSetOnIpBlock = true;
 
 /** @} */ # end of cookie settings }
 
@@ -8618,6 +8619,7 @@ $wgContentHandlerTextFallback = 'ignore';
  * handling is less robust and less flexible.
  *
  * @since 1.21
+ * @deprecated since 1.34, and should always be set true.
  */
 $wgContentHandlerUseDB = true;
 
@@ -8988,24 +8990,6 @@ $wgMultiContentRevisionSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_
  */
 $wgXmlDumpSchemaVersion = XML_DUMP_SCHEMA_VERSION_10;
 
-/**
- * Actor table schema migration stage.
- *
- * Use the SCHEMA_COMPAT_XXX flags. Supported values:
- * - SCHEMA_COMPAT_OLD
- * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
- * - SCHEMA_COMPAT_NEW
- *
- * Note that reading the old and new schema at the same time is not supported
- * in 1.32, but was (with significant query performance issues) in 1.31.
- *
- * @since 1.31
- * @since 1.32 changed allowed flags
- * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
- */
-$wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_NEW;
-
 /**
  * Flag to enable Partial Blocks. This allows an admin to prevent a user from editing specific pages
  * or namespaces.
index 6ae4371..c346b75 100644 (file)
@@ -1193,6 +1193,8 @@ class EditPage {
         * @since 1.21
         */
        protected function getContentObject( $def_content = null ) {
+               global $wgDisableAnonTalk;
+
                $content = false;
 
                $user = $this->context->getUser();
@@ -1292,8 +1294,11 @@ class EditPage {
                                                                                $undo
                                                                        )->inContentLanguage()->text();
                                                                } else {
+                                                                       $undoMessage = ( $undorev->getUser() === 0 && $wgDisableAnonTalk ) ?
+                                                                               'undo-summary-anon' :
+                                                                               'undo-summary';
                                                                        $undoSummary = $this->context->msg(
-                                                                               'undo-summary',
+                                                                               $undoMessage,
                                                                                $undo,
                                                                                $userText
                                                                        )->inContentLanguage()->text();
index 1241e1c..e31f9d2 100644 (file)
@@ -36,18 +36,18 @@ class FileDeleteForm {
        private $title = null;
 
        /**
-        * @var File
+        * @var LocalFile
         */
        private $file = null;
 
        /**
-        * @var File
+        * @var LocalFile
         */
        private $oldfile = null;
        private $oldimage = '';
 
        /**
-        * @param File $file File object we're deleting
+        * @param LocalFile $file File object we're deleting
         */
        public function __construct( $file ) {
                $this->title = $file->getTitle();
@@ -451,9 +451,9 @@ class FileDeleteForm {
         * value was provided, does it correspond to an
         * existing, local, old version of this file?
         *
-        * @param File &$file
-        * @param File &$oldfile
-        * @param File $oldimage
+        * @param LocalFile &$file
+        * @param LocalFile &$oldfile
+        * @param LocalFile $oldimage
         * @return bool
         */
        public static function haveDeletableFile( &$file, &$oldfile, $oldimage ) {
index f5eeb16..783dd43 100644 (file)
@@ -45,18 +45,27 @@ class TextFormatter implements ITextFormatter {
                return $this->langCode;
        }
 
-       private static function convertParam( MessageParam $param ) {
+       private function convertParam( MessageParam $param ) {
                if ( $param instanceof ListParam ) {
                        $convertedElements = [];
                        foreach ( $param->getValue() as $element ) {
-                               $convertedElements[] = self::convertParam( $element );
+                               $convertedElements[] = $this->convertParam( $element );
                        }
                        return Message::listParam( $convertedElements, $param->getListType() );
                } elseif ( $param instanceof MessageParam ) {
+                       $value = $param->getValue();
+                       if ( $value instanceof MessageValue ) {
+                               $mv = $value;
+                               $value = $this->createMessage( $mv->getKey() );
+                               foreach ( $mv->getParams() as $mvParam ) {
+                                       $value->params( $this->convertParam( $mvParam ) );
+                               }
+                       }
+
                        if ( $param->getType() === ParamType::TEXT ) {
-                               return $param->getValue();
+                               return $value;
                        } else {
-                               return [ $param->getType() => $param->getValue() ];
+                               return [ $param->getType() => $value ];
                        }
                } else {
                        throw new \InvalidArgumentException( 'Invalid message parameter type' );
@@ -66,7 +75,7 @@ class TextFormatter implements ITextFormatter {
        public function format( MessageValue $mv ) {
                $message = $this->createMessage( $mv->getKey() );
                foreach ( $mv->getParams() as $param ) {
-                       $message->params( self::convertParam( $param ) );
+                       $message->params( $this->convertParam( $param ) );
                }
                $message->inLanguage( $this->langCode );
                return $message->text();
index 564c8f4..634e7af 100644 (file)
@@ -473,6 +473,7 @@ class MovePage {
 
                        $mp = new MovePage( $oldSubpage, $newSubpage );
                        $method = $checkPermissions ? 'moveIfAllowed' : 'move';
+                       /** @var Status $status */
                        $status = $mp->$method( $user, $reason, $createRedirect, $changeTags );
                        if ( $status->isOK() ) {
                                $status->setResult( true, $newSubpage->getPrefixedText() );
@@ -508,7 +509,7 @@ class MovePage {
 
                Hooks::run( 'TitleMoveStarting', [ $this->oldTitle, $this->newTitle, $user ] );
 
-               $pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE );
+               $pageid = $this->oldTitle->getArticleID( Title::READ_LATEST );
                $protected = $this->oldTitle->isProtected();
 
                // Do the actual move; if this fails, it will throw an MWException(!)
index 15a759b..7f005fb 100644 (file)
@@ -2414,32 +2414,16 @@ class OutputPage extends ContextSource {
                                $this->mCdnMaxage != 0 &&
                                !$this->haveCacheVaryCookies()
                        ) {
-                               if ( $config->get( 'UseESI' ) ) {
-                                       wfDeprecated( '$wgUseESI = true', '1.33' );
-                                       # We'll purge the proxy cache explicitly, but require end user agents
-                                       # to revalidate against the proxy on each visit.
-                                       # Surrogate-Control controls our CDN, Cache-Control downstream caches
-                                       wfDebug( __METHOD__ .
-                                               ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
-                                       # start with a shorter timeout for initial testing
-                                       # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
-                                       $response->header(
-                                               "Surrogate-Control: max-age={$config->get( 'CdnMaxAge' )}" .
-                                               "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
-                                       );
-                                       $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
-                               } else {
-                                       # We'll purge the proxy cache for anons explicitly, but require end user agents
-                                       # to revalidate against the proxy on each visit.
-                                       # IMPORTANT! The CDN needs to replace the Cache-Control header with
-                                       # Cache-Control: s-maxage=0, must-revalidate, max-age=0
-                                       wfDebug( __METHOD__ .
-                                               ": local proxy caching; {$this->mLastModified} **", 'private' );
-                                       # start with a shorter timeout for initial testing
-                                       # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
-                                       $response->header( "Cache-Control: " .
-                                               "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
-                               }
+                               # We'll purge the proxy cache for anons explicitly, but require end user agents
+                               # to revalidate against the proxy on each visit.
+                               # IMPORTANT! The CDN needs to replace the Cache-Control header with
+                               # Cache-Control: s-maxage=0, must-revalidate, max-age=0
+                               wfDebug( __METHOD__ .
+                                       ": local proxy caching; {$this->mLastModified} **", 'private' );
+                               # start with a shorter timeout for initial testing
+                               # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
+                               $response->header( "Cache-Control: " .
+                                       "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
                        } else {
                                # We do want clients to cache if they can, but they *must* check for updates
                                # on revisiting the page.
index b63a84d..bf138c4 100644 (file)
@@ -31,8 +31,6 @@
  *
  * @note This class uses setter methods instead of a constructor so that
  * it can be compatible with PHP 4, PHP 5 and PHP 7 (without warnings).
- *
- * @class
  */
 class PHPVersionCheck {
        /* @var string The number of the MediaWiki version used. */
index 2882e66..4d7bd38 100644 (file)
@@ -401,4 +401,22 @@ class PathRouter {
 
                return $value;
        }
+
+       /**
+        * @internal For use by Title and WebRequest only.
+        * @param array $actionPaths
+        * @param string $articlePath
+        * @return string[]|false
+        */
+       public static function getActionPaths( array $actionPaths, $articlePath ) {
+               if ( !$actionPaths ) {
+                       return false;
+               }
+               // Processing of urls for this feature requires that 'view' is set.
+               // By default, set it to the pretty article path.
+               if ( !isset( $actionPaths['view'] ) ) {
+                       $actionPaths['view'] = $articlePath;
+               }
+               return $actionPaths;
+       }
 }
index f28b4ea..ee3441e 100644 (file)
@@ -6,6 +6,7 @@ use ExtensionRegistry;
 use MediaWiki;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
+use MediaWiki\Rest\Validator\Validator;
 use RequestContext;
 use Title;
 use WebResponse;
@@ -36,6 +37,7 @@ class EntryPoint {
 
                $services = MediaWikiServices::getInstance();
                $conf = $services->getMainConfig();
+               $objectFactory = $services->getObjectFactory();
 
                if ( !$conf->get( 'EnableRestAPI' ) ) {
                        wfHttpError( 403, 'Access Denied',
@@ -51,6 +53,9 @@ class EntryPoint {
                $authorizer = new MWBasicAuthorizer( $context->getUser(),
                        $services->getPermissionManager() );
 
+               // @phan-suppress-next-line PhanAccessMethodInternal
+               $restValidator = new Validator( $objectFactory, $request, RequestContext::getMain()->getUser() );
+
                global $IP;
                $router = new Router(
                        [ "$IP/includes/Rest/coreRoutes.json" ],
@@ -58,7 +63,9 @@ class EntryPoint {
                        $conf->get( 'RestPath' ),
                        $services->getLocalServerObjectCache(),
                        new ResponseFactory,
-                       $authorizer
+                       $authorizer,
+                       $objectFactory,
+                       $restValidator
                );
 
                $entryPoint = new self(
index c05d8e7..efe2b7e 100644 (file)
@@ -2,7 +2,18 @@
 
 namespace MediaWiki\Rest;
 
+use MediaWiki\Rest\Validator\BodyValidator;
+use MediaWiki\Rest\Validator\NullBodyValidator;
+use MediaWiki\Rest\Validator\Validator;
+
 abstract class Handler {
+
+       /**
+        * (string) ParamValidator constant to specify the source of the parameter.
+        * Value must be 'path', 'query', or 'post'.
+        */
+       const PARAM_SOURCE = 'rest-param-source';
+
        /** @var Router */
        private $router;
 
@@ -15,6 +26,12 @@ abstract class Handler {
        /** @var ResponseFactory */
        private $responseFactory;
 
+       /** @var array|null */
+       private $validatedParams;
+
+       /** @var mixed */
+       private $validatedBody;
+
        /**
         * Initialise with dependencies from the Router. This is called after construction.
         * @internal
@@ -68,6 +85,62 @@ abstract class Handler {
                return $this->responseFactory;
        }
 
+       /**
+        * Validate the request parameters/attributes and body. If there is a validation
+        * failure, a response with an error message should be returned or an
+        * HttpException should be thrown.
+        *
+        * @param Validator $restValidator
+        * @throws HttpException On validation failure.
+        */
+       public function validate( Validator $restValidator ) {
+               $validatedParams = $restValidator->validateParams( $this->getParamSettings() );
+               $validatedBody = $restValidator->validateBody( $this->request, $this );
+               $this->validatedParams = $validatedParams;
+               $this->validatedBody = $validatedBody;
+       }
+
+       /**
+        * Fetch ParamValidator settings for parameters
+        *
+        * Every setting must include self::PARAM_SOURCE to specify which part of
+        * the request is to contain the parameter.
+        *
+        * @return array[] Associative array mapping parameter names to
+        *  ParamValidator settings arrays
+        */
+       public function getParamSettings() {
+               return [];
+       }
+
+       /**
+        * Fetch the BodyValidator
+        * @param string $contentType Content type of the request.
+        * @return BodyValidator
+        */
+       public function getBodyValidator( $contentType ) {
+               return new NullBodyValidator();
+       }
+
+       /**
+        * Fetch the validated parameters
+        *
+        * @return array|null Array mapping parameter names to validated values,
+        *  or null if validateParams() was not called yet or validation failed.
+        */
+       public function getValidatedParams() {
+               return $this->validatedParams;
+       }
+
+       /**
+        * Fetch the validated body
+        * @return mixed Value returned by the body validator, or null if validateParams() was
+        *  not called yet, validation failed, there was no body, or the body was form data.
+        */
+       public function getValidatedBody() {
+               return $this->validatedBody;
+       }
+
        /**
         * The subclass should override this to provide the maximum last modified
         * timestamp for the current request. This is called before execute() in
index 34faee2..495b101 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace MediaWiki\Rest\Handler;
 
+use Wikimedia\ParamValidator\ParamValidator;
 use MediaWiki\Rest\SimpleHandler;
 
 /**
@@ -16,4 +17,14 @@ class HelloHandler extends SimpleHandler {
        public function needsWriteAccess() {
                return false;
        }
+
+       public function getParamSettings() {
+               return [
+                       'name' => [
+                               self::PARAM_SOURCE => 'path',
+                               ParamValidator::PARAM_TYPE => 'string',
+                               ParamValidator::PARAM_REQUIRED => true,
+                       ],
+               ];
+       }
 }
index ae6dde2..bcc414f 100644 (file)
@@ -8,7 +8,19 @@ namespace MediaWiki\Rest;
  * error response.
  */
 class HttpException extends \Exception {
-       public function __construct( $message, $code = 500 ) {
+
+       /** @var array|null */
+       private $errorData = null;
+
+       public function __construct( $message, $code = 500, $errorData = null ) {
                parent::__construct( $message, $code );
+               $this->errorData = $errorData;
+       }
+
+       /**
+        * @return array|null
+        */
+       public function getErrorData() {
+               return $this->errorData;
        }
 }
index d18cdb5..5e5a198 100644 (file)
@@ -175,8 +175,13 @@ class ResponseFactory {
        public function createFromException( $exception ) {
                if ( $exception instanceof HttpException ) {
                        // FIXME can HttpException represent 2xx or 3xx responses?
-                       $response = $this->createHttpError( $exception->getCode(),
-                               [ 'message' => $exception->getMessage() ] );
+                       $response = $this->createHttpError(
+                               $exception->getCode(),
+                               array_merge(
+                                       [ 'message' => $exception->getMessage() ],
+                                       (array)$exception->getErrorData()
+                               )
+                       );
                } else {
                        $response = $this->createHttpError( 500, [
                                'message' => 'Error: exception of type ' . get_class( $exception ),
index 961da01..a520130 100644 (file)
@@ -6,6 +6,7 @@ use AppendIterator;
 use BagOStuff;
 use MediaWiki\Rest\BasicAccess\BasicAuthorizerInterface;
 use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
+use MediaWiki\Rest\Validator\Validator;
 use Wikimedia\ObjectFactory;
 
 /**
@@ -44,6 +45,12 @@ class Router {
        /** @var BasicAuthorizerInterface */
        private $basicAuth;
 
+       /** @var ObjectFactory */
+       private $objectFactory;
+
+       /** @var Validator */
+       private $restValidator;
+
        /**
         * @param string[] $routeFiles List of names of JSON files containing routes
         * @param array $extraRoutes Extension route array
@@ -51,10 +58,13 @@ class Router {
         * @param BagOStuff $cacheBag A cache in which to store the matcher trees
         * @param ResponseFactory $responseFactory
         * @param BasicAuthorizerInterface $basicAuth
+        * @param ObjectFactory $objectFactory
+        * @param Validator $restValidator
         */
        public function __construct( $routeFiles, $extraRoutes, $rootPath,
                BagOStuff $cacheBag, ResponseFactory $responseFactory,
-               BasicAuthorizerInterface $basicAuth
+               BasicAuthorizerInterface $basicAuth, ObjectFactory $objectFactory,
+               Validator $restValidator
        ) {
                $this->routeFiles = $routeFiles;
                $this->extraRoutes = $extraRoutes;
@@ -62,6 +72,8 @@ class Router {
                $this->cacheBag = $cacheBag;
                $this->responseFactory = $responseFactory;
                $this->basicAuth = $basicAuth;
+               $this->objectFactory = $objectFactory;
+               $this->restValidator = $restValidator;
        }
 
        /**
@@ -245,9 +257,10 @@ class Router {
                $request->setPathParams( array_map( 'rawurldecode', $match['params'] ) );
                $spec = $match['userData'];
                $objectFactorySpec = array_intersect_key( $spec,
+                       // @todo ObjectFactory supports more keys than this.
                        [ 'factory' => true, 'class' => true, 'args' => true ] );
                /** @var $handler Handler (annotation for PHPStorm) */
-               $handler = ObjectFactory::getObjectFromSpec( $objectFactorySpec );
+               $handler = $this->objectFactory->createObject( $objectFactorySpec );
                $handler->init( $this, $request, $spec, $this->responseFactory );
 
                try {
@@ -268,6 +281,9 @@ class Router {
                if ( $authResult ) {
                        return $this->responseFactory->createHttpError( 403, [ 'error' => $authResult ] );
                }
+
+               $handler->validate( $this->restValidator );
+
                $response = $handler->execute();
                if ( !( $response instanceof ResponseInterface ) ) {
                        $response = $this->responseFactory->createFromReturnValue( $response );
index 3718d66..3c19e48 100644 (file)
@@ -14,7 +14,26 @@ namespace MediaWiki\Rest;
  */
 class SimpleHandler extends Handler {
        public function execute() {
-               $params = array_values( $this->getRequest()->getPathParams() );
+               $paramSettings = $this->getParamSettings();
+               $validatedParams = $this->getValidatedParams();
+               $unvalidatedParams = [];
+               $params = [];
+               foreach ( $this->getRequest()->getPathParams() as $name => $value ) {
+                       $source = $paramSettings[$name][self::PARAM_SOURCE] ?? 'unknown';
+                       if ( $source !== 'path' ) {
+                               $unvalidatedParams[] = $name;
+                               $params[] = $value;
+                       } else {
+                               $params[] = $validatedParams[$name];
+                       }
+               }
+
+               if ( $unvalidatedParams ) {
+                       throw new \LogicException(
+                               'Path parameters were not validated: ' . implode( ', ', $unvalidatedParams )
+                       );
+               }
+
                // @phan-suppress-next-line PhanUndeclaredMethod
                return $this->run( ...$params );
        }
diff --git a/includes/Rest/Validator/BodyValidator.php b/includes/Rest/Validator/BodyValidator.php
new file mode 100644 (file)
index 0000000..0147fa8
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\RequestInterface;
+
+/**
+ * Interface for validating a request body
+ */
+interface BodyValidator {
+
+       /**
+        * Validate the body of a request.
+        *
+        * This may return a data structure representing the parsed body. When used
+        * in the context of Handler::validateParams(), the returned value will be
+        * available to the handler via Handler::getValidatedBody().
+        *
+        * @param RequestInterface $request
+        * @return mixed
+        * @throws HttpException on validation failure
+        */
+       public function validateBody( RequestInterface $request );
+
+}
diff --git a/includes/Rest/Validator/NullBodyValidator.php b/includes/Rest/Validator/NullBodyValidator.php
new file mode 100644 (file)
index 0000000..4fba5fb
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use MediaWiki\Rest\RequestInterface;
+
+/**
+ * Do-nothing body validator
+ */
+class NullBodyValidator implements BodyValidator {
+
+       public function validateBody( RequestInterface $request ) {
+               return null;
+       }
+
+}
diff --git a/includes/Rest/Validator/ParamValidatorCallbacks.php b/includes/Rest/Validator/ParamValidatorCallbacks.php
new file mode 100644 (file)
index 0000000..6c54a50
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use InvalidArgumentException;
+use MediaWiki\Rest\RequestInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use User;
+use Wikimedia\ParamValidator\Callbacks;
+use Wikimedia\ParamValidator\ValidationException;
+
+class ParamValidatorCallbacks implements Callbacks {
+
+       /** @var RequestInterface */
+       private $request;
+
+       /** @var User */
+       private $user;
+
+       public function __construct( RequestInterface $request, User $user ) {
+               $this->request = $request;
+               $this->user = $user;
+       }
+
+       /**
+        * Get the raw parameters from a source in the request
+        * @param string $source 'path', 'query', or 'post'
+        * @return array
+        */
+       private function getParamsFromSource( $source ) {
+               switch ( $source ) {
+                       case 'path':
+                               return $this->request->getPathParams();
+
+                       case 'query':
+                               return $this->request->getQueryParams();
+
+                       case 'post':
+                               return $this->request->getPostParams();
+
+                       default:
+                               throw new InvalidArgumentException( __METHOD__ . ": Invalid source '$source'" );
+               }
+       }
+
+       public function hasParam( $name, array $options ) {
+               $params = $this->getParamsFromSource( $options['source'] );
+               return isset( $params[$name] );
+       }
+
+       public function getValue( $name, $default, array $options ) {
+               $params = $this->getParamsFromSource( $options['source'] );
+               return $params[$name] ?? $default;
+               // @todo Should normalization to NFC UTF-8 be done here (much like in the
+               // action API and the rest of MW), or should it be left to handlers to
+               // do whatever normalization they need?
+       }
+
+       public function hasUpload( $name, array $options ) {
+               if ( $options['source'] !== 'post' ) {
+                       return false;
+               }
+               return $this->getUploadedFile( $name, $options ) !== null;
+       }
+
+       public function getUploadedFile( $name, array $options ) {
+               if ( $options['source'] !== 'post' ) {
+                       return null;
+               }
+               $upload = $this->request->getUploadedFiles()[$name] ?? null;
+               return $upload instanceof UploadedFileInterface ? $upload : null;
+       }
+
+       public function recordCondition( ValidationException $condition, array $options ) {
+               // @todo Figure out how to handle warnings
+       }
+
+       public function useHighLimits( array $options ) {
+               return $this->user->isAllowed( 'apihighlimits' );
+       }
+
+}
diff --git a/includes/Rest/Validator/Validator.php b/includes/Rest/Validator/Validator.php
new file mode 100644 (file)
index 0000000..cee1cdb
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\RequestInterface;
+use User;
+use Wikimedia\ObjectFactory;
+use Wikimedia\ParamValidator\ParamValidator;
+use Wikimedia\ParamValidator\TypeDef\BooleanDef;
+use Wikimedia\ParamValidator\TypeDef\EnumDef;
+use Wikimedia\ParamValidator\TypeDef\FloatDef;
+use Wikimedia\ParamValidator\TypeDef\IntegerDef;
+use Wikimedia\ParamValidator\TypeDef\PasswordDef;
+use Wikimedia\ParamValidator\TypeDef\StringDef;
+use Wikimedia\ParamValidator\TypeDef\TimestampDef;
+use Wikimedia\ParamValidator\TypeDef\UploadDef;
+use Wikimedia\ParamValidator\ValidationException;
+
+/**
+ * Wrapper for ParamValidator
+ *
+ * It's intended to be used in the REST API classes by composition.
+ *
+ * @since 1.34
+ */
+class Validator {
+
+       /** @var array Type defs for ParamValidator */
+       private static $typeDefs = [
+               'boolean' => [ 'class' => BooleanDef::class ],
+               'enum' => [ 'class' => EnumDef::class ],
+               'integer' => [ 'class' => IntegerDef::class ],
+               'float' => [ 'class' => FloatDef::class ],
+               'double' => [ 'class' => FloatDef::class ],
+               'NULL' => [
+                       'class' => StringDef::class,
+                       'args' => [ [
+                               'allowEmptyWhenRequired' => true,
+                       ] ],
+               ],
+               'password' => [ 'class' => PasswordDef::class ],
+               'string' => [ 'class' => StringDef::class ],
+               'timestamp' => [ 'class' => TimestampDef::class ],
+               'upload' => [ 'class' => UploadDef::class ],
+       ];
+
+       /** @var string[] HTTP request methods that we expect never to have a payload */
+       private static $noBodyMethods = [ 'GET', 'HEAD', 'DELETE' ];
+
+       /** @var string[] HTTP request methods that we expect always to have a payload */
+       private static $bodyMethods = [ 'POST', 'PUT' ];
+
+       /** @var string[] Content types handled via $_POST */
+       private static $formDataContentTypes = [
+               'application/x-www-form-urlencoded',
+               'multipart/form-data',
+       ];
+
+       /** @var ParamValidator */
+       private $paramValidator;
+
+       /**
+        * @internal
+        * @param ObjectFactory $objectFactory
+        * @param RequestInterface $request
+        * @param User $user
+        */
+       public function __construct(
+               ObjectFactory $objectFactory, RequestInterface $request, User $user
+       ) {
+               $this->paramValidator = new ParamValidator(
+                       new ParamValidatorCallbacks( $request, $user ),
+                       $objectFactory,
+                       [
+                               'typeDefs' => self::$typeDefs,
+                       ]
+               );
+       }
+
+       /**
+        * Validate parameters
+        * @param array[] $paramSettings Parameter settings
+        * @return array Validated parameters
+        * @throws HttpException on validaton failure
+        */
+       public function validateParams( array $paramSettings ) {
+               $validatedParams = [];
+               foreach ( $paramSettings as $name => $settings ) {
+                       try {
+                               $validatedParams[$name] = $this->paramValidator->getValue( $name, $settings, [
+                                       'source' => $settings[Handler::PARAM_SOURCE] ?? 'unspecified',
+                               ] );
+                       } catch ( ValidationException $e ) {
+                               throw new HttpException( 'Parameter validation failed', 400, [
+                                       'error' => 'parameter-validation-failed',
+                                       'name' => $e->getParamName(),
+                                       'value' => $e->getParamValue(),
+                                       'failureCode' => $e->getFailureCode(),
+                                       'failureData' => $e->getFailureData(),
+                               ] );
+                       }
+               }
+               return $validatedParams;
+       }
+
+       /**
+        * Validate the body of a request.
+        *
+        * This may return a data structure representing the parsed body. When used
+        * in the context of Handler::validateParams(), the returned value will be
+        * available to the handler via Handler::getValidatedBody().
+        *
+        * @param RequestInterface $request
+        * @param Handler $handler Used to call getBodyValidator()
+        * @return mixed May be null
+        * @throws HttpException on validation failure
+        */
+       public function validateBody( RequestInterface $request, Handler $handler ) {
+               $method = strtoupper( trim( $request->getMethod() ) );
+
+               // If the method should never have a body, don't bother validating.
+               if ( in_array( $method, self::$noBodyMethods, true ) ) {
+                       return null;
+               }
+
+               // Get the content type
+               list( $ct ) = explode( ';', $request->getHeaderLine( 'Content-Type' ), 2 );
+               $ct = strtolower( trim( $ct ) );
+               if ( $ct === '' ) {
+                       // No Content-Type was supplied. RFC 7231 § 3.1.1.5 allows this, but since it's probably a
+                       // client error let's return a 415. But don't 415 for unknown methods and an empty body.
+                       if ( !in_array( $method, self::$bodyMethods, true ) ) {
+                               $body = $request->getBody();
+                               $size = $body->getSize();
+                               if ( $size === null ) {
+                                       // No size available. Try reading 1 byte.
+                                       if ( $body->isSeekable() ) {
+                                               $body->rewind();
+                                       }
+                                       $size = $body->read( 1 ) === '' ? 0 : 1;
+                               }
+                               if ( $size === 0 ) {
+                                       return null;
+                               }
+                       }
+                       throw new HttpException( "A Content-Type header must be supplied with a request payload.", 415, [
+                               'error' => 'no-content-type',
+                       ] );
+               }
+
+               // Form data is parsed into $_POST and $_FILES by PHP and from there is accessed as parameters,
+               // don't bother trying to handle these via BodyValidator too.
+               if ( in_array( $ct, self::$formDataContentTypes, true ) ) {
+                       return null;
+               }
+
+               // Validate the body. BodyValidator throws an HttpException on failure.
+               return $handler->getBodyValidator( $ct )->validateBody( $request );
+       }
+
+}
index c6e727e..828f647 100644 (file)
@@ -298,203 +298,6 @@ class Revision implements IDBAccessObject {
                return $rec ? new Revision( $rec ) : null;
        }
 
-       /**
-        * Return the value of a select() JOIN conds array for the user table.
-        * This will get user table rows for logged-in users.
-        * @since 1.19
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
-        * @return array
-        */
-       public static function userJoinCond() {
-               global $wgActorTableSchemaMigrationStage;
-
-               wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's
-                       // no way the join it's trying to do can work once the old fields
-                       // aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
-       }
-
-       /**
-        * Return the value of a select() page conds array for the page table.
-        * This will assure that the revision(s) are not orphaned from live pages.
-        * @since 1.19
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
-        * @return array
-        */
-       public static function pageJoinCond() {
-               wfDeprecated( __METHOD__, '1.31' );
-               return [ 'JOIN', [ 'page_id = rev_page' ] ];
-       }
-
-       /**
-        * Return the list of revision fields that should be selected to create
-        * a new revision.
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
-        * @return array
-        */
-       public static function selectFields() {
-               global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
-               global $wgMultiContentRevisionSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->rev_user or $row->rev_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->rev_text_id or $row->rev_content_model and we can't give it
-                       // useful values here once those aren't being written anymore,
-                       // and may not exist at all.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgMultiContentRevisionSchemaMigrationStage '
-                               . 'does not have SCHEMA_COMPAT_WRITE_OLD set.'
-                       );
-               }
-
-               wfDeprecated( __METHOD__, '1.31' );
-
-               $fields = [
-                       'rev_id',
-                       'rev_page',
-                       'rev_text_id',
-                       'rev_timestamp',
-                       'rev_user_text',
-                       'rev_user',
-                       'rev_actor' => 'NULL',
-                       'rev_minor_edit',
-                       'rev_deleted',
-                       'rev_len',
-                       'rev_parent_id',
-                       'rev_sha1',
-               ];
-
-               $fields += CommentStore::getStore()->getFields( 'rev_comment' );
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'rev_content_format';
-                       $fields[] = 'rev_content_model';
-               }
-
-               return $fields;
-       }
-
-       /**
-        * Return the list of revision fields that should be selected to create
-        * a new revision from an archive row.
-        * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() instead.
-        * @return array
-        */
-       public static function selectArchiveFields() {
-               global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
-               global $wgMultiContentRevisionSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->ar_user or $row->ar_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->ar_text_id or $row->ar_content_model and we can't give it
-                       // useful values here once those aren't being written anymore,
-                       // and may not exist at all.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__ . ' when $wgMultiContentRevisionSchemaMigrationStage '
-                               . 'does not have SCHEMA_COMPAT_WRITE_OLD set.'
-                       );
-               }
-
-               wfDeprecated( __METHOD__, '1.31' );
-
-               $fields = [
-                       'ar_id',
-                       'ar_page_id',
-                       'ar_rev_id',
-                       'ar_text_id',
-                       'ar_timestamp',
-                       'ar_user_text',
-                       'ar_user',
-                       'ar_actor' => 'NULL',
-                       'ar_minor_edit',
-                       'ar_deleted',
-                       'ar_len',
-                       'ar_parent_id',
-                       'ar_sha1',
-               ];
-
-               $fields += CommentStore::getStore()->getFields( 'ar_comment' );
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'ar_content_format';
-                       $fields[] = 'ar_content_model';
-               }
-               return $fields;
-       }
-
-       /**
-        * Return the list of text fields that should be selected to read the
-        * revision text
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'text' ] ) instead.
-        * @return array
-        */
-       public static function selectTextFields() {
-               wfDeprecated( __METHOD__, '1.31' );
-               return [
-                       'old_text',
-                       'old_flags'
-               ];
-       }
-
-       /**
-        * Return the list of page fields that should be selected from page table
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
-        * @return array
-        */
-       public static function selectPageFields() {
-               wfDeprecated( __METHOD__, '1.31' );
-               return [
-                       'page_namespace',
-                       'page_title',
-                       'page_id',
-                       'page_latest',
-                       'page_is_redirect',
-                       'page_len',
-               ];
-       }
-
-       /**
-        * Return the list of user fields that should be selected from user table
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
-        * @return array
-        */
-       public static function selectUserFields() {
-               wfDeprecated( __METHOD__, '1.31' );
-               return [ 'user_name' ];
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new revision object.
@@ -1202,7 +1005,7 @@ class Revision implements IDBAccessObject {
 
                $comment = CommentStoreComment::newUnsavedComment( $summary, null );
 
-               $title = Title::newFromID( $pageId, Title::GAID_FOR_UPDATE );
+               $title = Title::newFromID( $pageId, Title::READ_LATEST );
                if ( $title === null ) {
                        return null;
                }
index 73f622a..735a212 100644 (file)
@@ -326,10 +326,10 @@ class RevisionStore
 
                $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->dbDomain === false );
                list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
-               $titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
 
                // Loading by ID is best, but Title::newFromID does not support that for foreign IDs.
                if ( $canUseTitleNewFromId ) {
+                       $titleFlags = ( $dbMode == DB_MASTER ? Title::READ_LATEST : 0 );
                        // TODO: better foreign title handling (introduce TitleFactory)
                        $title = Title::newFromID( $pageId, $titleFlags );
                        if ( $title ) {
index 740377c..0b0aaf5 100644 (file)
@@ -77,9 +77,7 @@ use Wikimedia\ObjectFactory;
 
 return [
        'ActorMigration' => function ( MediaWikiServices $services ) : ActorMigration {
-               return new ActorMigration(
-                       $services->getMainConfig()->get( 'ActorTableSchemaMigrationStage' )
-               );
+               return new ActorMigration( SCHEMA_COMPAT_NEW );
        },
 
        'BadFileLookup' => function ( MediaWikiServices $services ) : BadFileLookup {
@@ -734,7 +732,8 @@ return [
                return new SpecialPageFactory(
                        new ServiceOptions(
                                SpecialPageFactory::$constructorOptions, $services->getMainConfig() ),
-                       $services->getContentLanguage()
+                       $services->getContentLanguage(),
+                       $services->getObjectFactory()
                );
        },
 
index e55fbe8..518531a 100644 (file)
@@ -52,6 +52,17 @@ if ( ini_get( 'mbstring.func_overload' ) ) {
        die( 'MediaWiki does not support installations where mbstring.func_overload is non-zero.' );
 }
 
+// Define MW_ENTRY_POINT if it's not already, so that config code can check the
+// value without using defined()
+if ( !defined( 'MW_ENTRY_POINT' ) ) {
+       /**
+        * The entry point, which may be either the script filename without the
+        * file extension, or "cli" for maintenance scripts, or "unknown" for any
+        * entry point that does not set the constant.
+        */
+       define( 'MW_ENTRY_POINT', 'unknown' );
+}
+
 // Start the autoloader, so that extensions can derive classes from core files
 require_once "$IP/includes/AutoLoader.php";
 
@@ -156,12 +167,6 @@ if ( $wgArticlePath === false ) {
        }
 }
 
-if ( !empty( $wgActionPaths ) && !isset( $wgActionPaths['view'] ) ) {
-       // 'view' is assumed the default action path everywhere in the code
-       // but is rarely filled in $wgActionPaths
-       $wgActionPaths['view'] = $wgArticlePath;
-}
-
 if ( $wgResourceBasePath === null ) {
        $wgResourceBasePath = $wgScriptPath;
 }
@@ -803,22 +808,17 @@ if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
        // Initialize the session
        try {
                $session = MediaWiki\Session\SessionManager::getGlobalSession();
-       } catch ( OverflowException $ex ) {
-               if ( isset( $ex->sessionInfos ) && count( $ex->sessionInfos ) >= 2 ) {
-                       // The exception is because the request had multiple possible
-                       // sessions tied for top priority. Report this to the user.
-                       $list = [];
-                       foreach ( $ex->sessionInfos as $info ) {
-                               $list[] = $info->getProvider()->describe( $wgContLang );
-                       }
-                       $list = $wgContLang->listToText( $list );
-                       throw new HttpError( 400,
-                               Message::newFromKey( 'sessionmanager-tie', $list )->inLanguage( $wgContLang )->plain()
-                       );
+       } catch ( MediaWiki\Session\SessionOverflowException $ex ) {
+               // The exception is because the request had multiple possible
+               // sessions tied for top priority. Report this to the user.
+               $list = [];
+               foreach ( $ex->getSessionInfos() as $info ) {
+                       $list[] = $info->getProvider()->describe( $wgContLang );
                }
-
-               // Not the one we want, rethrow
-               throw $ex;
+               $list = $wgContLang->listToText( $list );
+               throw new HttpError( 400,
+                       Message::newFromKey( 'sessionmanager-tie', $list )->inLanguage( $wgContLang )->plain()
+               );
        }
 
        if ( $session->isPersistent() ) {
index 547b28c..1e93c44 100644 (file)
@@ -51,10 +51,11 @@ class Title implements LinkTarget, IDBAccessObject {
        const CACHE_MAX = 1000;
 
        /**
-        * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends
-        * to use the master DB
+        * Used to be GAID_FOR_UPDATE define(). Used with getArticleID() and friends
+        * to use the master DB and inject it into link cache.
+        * @deprecated since 1.34, use Title::READ_LATEST instead.
         */
-       const GAID_FOR_UPDATE = 1;
+       const GAID_FOR_UPDATE = 512;
 
        /**
         * Flag for use with factory methods like newFromLinkTarget() that have
@@ -74,25 +75,18 @@ class Title implements LinkTarget, IDBAccessObject {
 
        /** @var string Text form (spaces not underscores) of the main part */
        public $mTextform = '';
-
        /** @var string URL-encoded form of the main part */
        public $mUrlform = '';
-
        /** @var string Main part with underscores */
        public $mDbkeyform = '';
-
        /** @var string Database key with the initial letter in the case specified by the user */
        protected $mUserCaseDBKey;
-
        /** @var int Namespace index, i.e. one of the NS_xxxx constants */
        public $mNamespace = NS_MAIN;
-
        /** @var string Interwiki prefix */
        public $mInterwiki = '';
-
        /** @var bool Was this Title created from a string with a local interwiki prefix? */
        private $mLocalInterwiki = false;
-
        /** @var string Title fragment (i.e. the bit after the #) */
        public $mFragment = '';
 
@@ -467,16 +461,18 @@ class Title implements LinkTarget, IDBAccessObject {
         * Create a new Title from an article ID
         *
         * @param int $id The page_id corresponding to the Title to create
-        * @param int $flags Use Title::GAID_FOR_UPDATE to use master
+        * @param int $flags Bitfield of class READ_* constants
         * @return Title|null The new object, or null on an error
         */
        public static function newFromID( $id, $flags = 0 ) {
-               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
-               $row = $db->selectRow(
+               $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
+               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+               $row = wfGetDB( $index )->selectRow(
                        'page',
                        self::getSelectFields(),
                        [ 'page_id' => $id ],
-                       __METHOD__
+                       __METHOD__,
+                       $options
                );
                if ( $row !== false ) {
                        $title = self::newFromRow( $row );
@@ -545,10 +541,10 @@ class Title implements LinkTarget, IDBAccessObject {
                        if ( isset( $row->page_latest ) ) {
                                $this->mLatestID = (int)$row->page_latest;
                        }
-                       if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
-                               $this->mContentModel = (string)$row->page_content_model;
-                       } elseif ( !$this->mForcedContentModel ) {
-                               $this->mContentModel = false; # initialized lazily in getContentModel()
+                       if ( isset( $row->page_content_model ) ) {
+                               $this->lazyFillContentModel( $row->page_content_model );
+                       } else {
+                               $this->lazyFillContentModel( false ); // lazily-load getContentModel()
                        }
                        if ( isset( $row->page_lang ) ) {
                                $this->mDbPageLanguage = (string)$row->page_lang;
@@ -561,9 +557,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
-                       if ( !$this->mForcedContentModel ) {
-                               $this->mContentModel = false; # initialized lazily in getContentModel()
-                       }
+                       $this->lazyFillContentModel( false ); // lazily-load getContentModel()
                }
        }
 
@@ -598,7 +592,6 @@ class Title implements LinkTarget, IDBAccessObject {
                $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
                $t->mUrlform = wfUrlencode( $t->mDbkeyform );
                $t->mTextform = strtr( $title, '_', ' ' );
-               $t->mContentModel = false; # initialized lazily in getContentModel()
                return $t;
        }
 
@@ -676,7 +669,7 @@ class Title implements LinkTarget, IDBAccessObject {
         * Get the prefixed DB key associated with an ID
         *
         * @param int $id The page_id of the article
-        * @return Title|null An object representing the article, or null if no such article was found
+        * @return string|null An object representing the article, or null if no such article was found
         */
        public static function nameOf( $id ) {
                $dbr = wfGetDB( DB_REPLICA );
@@ -691,8 +684,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        return null;
                }
 
-               $n = self::makeName( $s->page_namespace, $s->page_title );
-               return $n;
+               return self::makeName( $s->page_namespace, $s->page_title );
        }
 
        /**
@@ -1051,21 +1043,31 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @todo Deprecate this in favor of SlotRecord::getModel()
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return string Content model id
         */
        public function getContentModel( $flags = 0 ) {
-               if ( !$this->mForcedContentModel
-                       && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
-                       && $this->getArticleID( $flags )
+               if ( $this->mForcedContentModel ) {
+                       if ( !$this->mContentModel ) {
+                               throw new RuntimeException( 'Got out of sync; an empty model is being forced' );
+                       }
+                       // Content model is locked to the currently loaded one
+                       return $this->mContentModel;
+               }
+
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->lazyFillContentModel( $this->loadFieldFromDB( 'page_content_model', $flags ) );
+               } elseif (
+                       ( !$this->mContentModel || $flags & self::GAID_FOR_UPDATE ) &&
+                       $this->getArticleId( $flags )
                ) {
                        $linkCache = MediaWikiServices::getInstance()->getLinkCache();
                        $linkCache->addLinkObj( $this ); # in case we already had an article ID
-                       $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
+                       $this->lazyFillContentModel( $linkCache->getGoodLinkFieldObj( $this, 'model' ) );
                }
 
                if ( !$this->mContentModel ) {
-                       $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
+                       $this->lazyFillContentModel( ContentHandler::getDefaultModelFor( $this ) );
                }
 
                return $this->mContentModel;
@@ -1082,21 +1084,38 @@ class Title implements LinkTarget, IDBAccessObject {
        }
 
        /**
-        * Set a proposed content model for the page for permissions
-        * checking. This does not actually change the content model
-        * of a title!
+        * Set a proposed content model for the page for permissions checking
+        *
+        * This does not actually change the content model of a title in the DB.
+        * It only affects this particular Title instance. The content model is
+        * forced to remain this value until another setContentModel() call.
         *
-        * Additionally, you should make sure you've checked
-        * ContentHandler::canBeUsedOn() first.
+        * ContentHandler::canBeUsedOn() should be checked before calling this
+        * if there is any doubt regarding the applicability of the content model
         *
         * @since 1.28
         * @param string $model CONTENT_MODEL_XXX constant
         */
        public function setContentModel( $model ) {
+               if ( (string)$model === '' ) {
+                       throw new InvalidArgumentException( "Missing CONTENT_MODEL_* constant" );
+               }
+
                $this->mContentModel = $model;
                $this->mForcedContentModel = true;
        }
 
+       /**
+        * If the content model field is not frozen then update it with a retreived value
+        *
+        * @param string|bool $model CONTENT_MODEL_XXX constant or false
+        */
+       private function lazyFillContentModel( $model ) {
+               if ( !$this->mForcedContentModel ) {
+                       $this->mContentModel = ( $model === false ) ? false : (string)$model;
+               }
+       }
+
        /**
         * Get the namespace text
         *
@@ -2068,16 +2087,18 @@ class Title implements LinkTarget, IDBAccessObject {
                                $url = false;
                                $matches = [];
 
-                               if ( !empty( $wgActionPaths )
+                               $articlePaths = PathRouter::getActionPaths( $wgActionPaths, $wgArticlePath );
+
+                               if ( $articlePaths
                                        && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
                                ) {
                                        $action = urldecode( $matches[2] );
-                                       if ( isset( $wgActionPaths[$action] ) ) {
+                                       if ( isset( $articlePaths[$action] ) ) {
                                                $query = $matches[1];
                                                if ( isset( $matches[4] ) ) {
                                                        $query .= $matches[4];
                                                }
-                                               $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
+                                               $url = str_replace( '$1', $dbkey, $articlePaths[$action] );
                                                if ( $query != '' ) {
                                                        $url = wfAppendQuery( $url, $query );
                                                }
@@ -2796,10 +2817,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        return;
                }
 
-               // TODO: should probably pass $flags into getArticleID, but it seems hacky
-               // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
-               // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
-               $id = $this->getArticleID();
+               $id = $this->getArticleID( $flags );
                if ( $id ) {
                        $fname = __METHOD__;
                        $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
@@ -3023,24 +3041,28 @@ class Title implements LinkTarget, IDBAccessObject {
         * Get the article ID for this Title from the link cache,
         * adding it if necessary
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select
-        *  for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return int The ID
         */
        public function getArticleID( $flags = 0 ) {
                if ( $this->mNamespace < 0 ) {
                        $this->mArticleID = 0;
+
                        return $this->mArticleID;
                }
+
                $linkCache = MediaWikiServices::getInstance()->getLinkCache();
                if ( $flags & self::GAID_FOR_UPDATE ) {
                        $oldUpdate = $linkCache->forUpdate( true );
                        $linkCache->clearLink( $this );
                        $this->mArticleID = $linkCache->addLinkObj( $this );
                        $linkCache->forUpdate( $oldUpdate );
+               } elseif ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mArticleID = (int)$this->loadFieldFromDB( 'page_id', $flags );
                } elseif ( $this->mArticleID == -1 ) {
                        $this->mArticleID = $linkCache->addLinkObj( $this );
                }
+
                return $this->mArticleID;
        }
 
@@ -3048,33 +3070,27 @@ class Title implements LinkTarget, IDBAccessObject {
         * Is this an article that is a redirect page?
         * Uses link cache, adding it if necessary
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return bool
         */
        public function isRedirect( $flags = 0 ) {
-               if ( !is_null( $this->mRedirect ) ) {
-                       return $this->mRedirect;
-               }
-               if ( !$this->getArticleID( $flags ) ) {
-                       $this->mRedirect = false;
-                       return $this->mRedirect;
-               }
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mRedirect = (bool)$this->loadFieldFromDB( 'page_is_redirect', $flags );
+               } else {
+                       if ( $this->mRedirect !== null ) {
+                               return $this->mRedirect;
+                       } elseif ( !$this->getArticleID( $flags ) ) {
+                               $this->mRedirect = false;
 
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->addLinkObj( $this ); # in case we already had an article ID
-               $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
-               if ( $cached === null ) {
-                       # Trust LinkCache's state over our own
-                       # LinkCache is telling us that the page doesn't exist, despite there being cached
-                       # data relating to an existing page in $this->mArticleID. Updaters should clear
-                       # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
-                       # set, then LinkCache will definitely be up to date here, since getArticleID() forces
-                       # LinkCache to refresh its data from the master.
-                       $this->mRedirect = false;
-                       return $this->mRedirect;
-               }
+                               return $this->mRedirect;
+                       }
 
-               $this->mRedirect = (bool)$cached;
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
+                       $linkCache->addLinkObj( $this ); // in case we already had an article ID
+                       // Note that LinkCache returns null if it thinks the page does not exist;
+                       // always trust the state of LinkCache over that of this Title instance.
+                       $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+               }
 
                return $this->mRedirect;
        }
@@ -3083,27 +3099,26 @@ class Title implements LinkTarget, IDBAccessObject {
         * What is the length of this page?
         * Uses link cache, adding it if necessary
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return int
         */
        public function getLength( $flags = 0 ) {
-               if ( $this->mLength != -1 ) {
-                       return $this->mLength;
-               }
-               if ( !$this->getArticleID( $flags ) ) {
-                       $this->mLength = 0;
-                       return $this->mLength;
-               }
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->addLinkObj( $this ); # in case we already had an article ID
-               $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
-               if ( $cached === null ) {
-                       # Trust LinkCache's state over our own, as for isRedirect()
-                       $this->mLength = 0;
-                       return $this->mLength;
-               }
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mLength = (int)$this->loadFieldFromDB( 'page_len', $flags );
+               } else {
+                       if ( $this->mLength != -1 ) {
+                               return $this->mLength;
+                       } elseif ( !$this->getArticleID( $flags ) ) {
+                               $this->mLength = 0;
+                               return $this->mLength;
+                       }
 
-               $this->mLength = intval( $cached );
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
+                       $linkCache->addLinkObj( $this ); // in case we already had an article ID
+                       // Note that LinkCache returns null if it thinks the page does not exist;
+                       // always trust the state of LinkCache over that of this Title instance.
+                       $this->mLength = (int)$linkCache->getGoodLinkFieldObj( $this, 'length' );
+               }
 
                return $this->mLength;
        }
@@ -3111,49 +3126,46 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * What is the page_latest field for this page?
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return int Int or 0 if the page doesn't exist
         */
        public function getLatestRevID( $flags = 0 ) {
-               if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
-                       return intval( $this->mLatestID );
-               }
-               if ( !$this->getArticleID( $flags ) ) {
-                       $this->mLatestID = 0;
-                       return $this->mLatestID;
-               }
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->addLinkObj( $this ); # in case we already had an article ID
-               $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
-               if ( $cached === null ) {
-                       # Trust LinkCache's state over our own, as for isRedirect()
-                       $this->mLatestID = 0;
-                       return $this->mLatestID;
-               }
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mLatestID = (int)$this->loadFieldFromDB( 'page_latest', $flags );
+               } else {
+                       if ( $this->mLatestID !== false ) {
+                               return (int)$this->mLatestID;
+                       } elseif ( !$this->getArticleID( $flags ) ) {
+                               $this->mLatestID = 0;
+
+                               return $this->mLatestID;
+                       }
 
-               $this->mLatestID = intval( $cached );
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
+                       $linkCache->addLinkObj( $this ); // in case we already had an article ID
+                       // Note that LinkCache returns null if it thinks the page does not exist;
+                       // always trust the state of LinkCache over that of this Title instance.
+                       $this->mLatestID = (int)$linkCache->getGoodLinkFieldObj( $this, 'revision' );
+               }
 
                return $this->mLatestID;
        }
 
        /**
-        * This clears some fields in this object, and clears any associated
-        * keys in the "bad links" section of the link cache.
+        * Inject a page ID, reset DB-loaded fields, and clear the link cache for this title
+        *
+        * This can be called on page insertion to allow loading of the new page_id without
+        * having to create a new Title instance. Likewise with deletion.
         *
-        * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow
-        * loading of the new page_id. It's also called from
-        * WikiPage::doDeleteArticleReal()
+        * @note This overrides Title::setContentModel()
         *
-        * @param int $newid The new Article ID
+        * @param int|bool $id Page ID, 0 for non-existant, or false for "unknown" (lazy-load)
         */
-       public function resetArticleID( $newid ) {
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->clearLink( $this );
-
-               if ( $newid === false ) {
+       public function resetArticleID( $id ) {
+               if ( $id === false ) {
                        $this->mArticleID = -1;
                } else {
-                       $this->mArticleID = intval( $newid );
+                       $this->mArticleID = (int)$id;
                }
                $this->mRestrictionsLoaded = false;
                $this->mRestrictions = [];
@@ -3162,10 +3174,13 @@ class Title implements LinkTarget, IDBAccessObject {
                $this->mLength = -1;
                $this->mLatestID = false;
                $this->mContentModel = false;
+               $this->mForcedContentModel = false;
                $this->mEstimateRevisions = null;
                $this->mPageLanguage = null;
                $this->mDbPageLanguage = false;
                $this->mIsBigDeletion = null;
+
+               MediaWikiServices::getInstance()->getLinkCache()->clearLink( $this );
        }
 
        public static function clearCaches() {
@@ -3499,6 +3514,7 @@ class Title implements LinkTarget, IDBAccessObject {
 
                $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $this, $nt );
                $method = $auth ? 'moveIfAllowed' : 'move';
+               /** @var Status $status */
                $status = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
                if ( $status->isOK() ) {
                        return true;
@@ -3531,6 +3547,7 @@ class Title implements LinkTarget, IDBAccessObject {
 
                $mp = new MovePage( $this, $nt );
                $method = $auth ? 'moveSubpagesIfAllowed' : 'moveSubpages';
+               /** @var Status $result */
                $result = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
 
                if ( !$result->isOK() ) {
@@ -3539,6 +3556,7 @@ class Title implements LinkTarget, IDBAccessObject {
 
                $retval = [];
                foreach ( $result->getValue() as $key => $status ) {
+                       /** @var Status $status */
                        if ( $status->isOK() ) {
                                $retval[$key] = $status->getValue();
                        } else {
@@ -3549,8 +3567,9 @@ class Title implements LinkTarget, IDBAccessObject {
        }
 
        /**
-        * Checks if this page is just a one-rev redirect.
-        * Adds lock, so don't use just for light purposes.
+        * Locks the page row and check if this page is single revision redirect
+        *
+        * This updates the cached fields of this instance via Title::loadFromRow()
         *
         * @return bool
         */
@@ -3730,24 +3749,22 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get next/previous revision ID relative to another revision ID
         * @param int $revId Revision ID. Get the revision that was before this one.
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @param string $dir 'next' or 'prev'
         * @return int|bool New revision ID, or false if none exists
         */
        private function getRelativeRevisionID( $revId, $flags, $dir ) {
                $rl = MediaWikiServices::getInstance()->getRevisionLookup();
-               $rlFlags = $flags === self::GAID_FOR_UPDATE ? IDBAccessObject::READ_LATEST : 0;
-               $rev = $rl->getRevisionById( $revId, $rlFlags );
+               $rev = $rl->getRevisionById( $revId, $flags );
                if ( !$rev ) {
                        return false;
                }
-               $oldRev = $dir === 'next'
-                       ? $rl->getNextRevision( $rev, $rlFlags )
-                       : $rl->getPreviousRevision( $rev, $rlFlags );
-               if ( !$oldRev ) {
-                       return false;
-               }
-               return $oldRev->getId();
+
+               $oldRev = ( $dir === 'next' )
+                       ? $rl->getNextRevision( $rev, $flags )
+                       : $rl->getPreviousRevision( $rev, $flags );
+
+               return $oldRev ? $oldRev->getId() : false;
        }
 
        /**
@@ -3755,7 +3772,7 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @deprecated since 1.34, use RevisionLookup::getPreviousRevision
         * @param int $revId Revision ID. Get the revision that was before this one.
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return int|bool Old revision ID, or false if none exists
         */
        public function getPreviousRevisionID( $revId, $flags = 0 ) {
@@ -3767,7 +3784,7 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @deprecated since 1.34, use RevisionLookup::getNextRevision
         * @param int $revId Revision ID. Get the revision that was after this one.
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return int|bool Next revision ID, or false if none exists
         */
        public function getNextRevisionID( $revId, $flags = 0 ) {
@@ -3777,21 +3794,26 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get the first revision of the page
         *
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return Revision|null If page doesn't exist
         */
        public function getFirstRevision( $flags = 0 ) {
                $pageId = $this->getArticleID( $flags );
                if ( $pageId ) {
-                       $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
+                       $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
+                       list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
                        $revQuery = Revision::getQueryInfo();
-                       $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
+                       $row = wfGetDB( $index )->selectRow(
+                               $revQuery['tables'], $revQuery['fields'],
                                [ 'rev_page' => $pageId ],
                                __METHOD__,
-                               [
-                                       'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
-                                       'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
-                               ],
+                               array_merge(
+                                       [
+                                               'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
+                                               'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
+                                       ],
+                                       $options
+                               ),
                                $revQuery['joins']
                        );
                        if ( $row ) {
@@ -3804,7 +3826,7 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get the oldest revision timestamp of this page
         *
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return string|null MW timestamp
         */
        public function getEarliestRevTime( $flags = 0 ) {
@@ -4035,8 +4057,7 @@ class Title implements LinkTarget, IDBAccessObject {
         * If you want to know if a title can be meaningfully viewed, you should
         * probably call the isKnown() method instead.
         *
-        * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check
-        *   from master/for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return bool
         */
        public function exists( $flags = 0 ) {
@@ -4632,6 +4653,27 @@ class Title implements LinkTarget, IDBAccessObject {
                return $notices;
        }
 
+       /**
+        * @param int $flags Bitfield of class READ_* constants
+        * @return string|bool
+        */
+       private function loadFieldFromDB( $field, $flags ) {
+               if ( !in_array( $field, self::getSelectFields(), true ) ) {
+                       return false; // field does not exist
+               }
+
+               $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
+               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+
+               return wfGetDB( $index )->selectField(
+                       'page',
+                       $field,
+                       $this->pageCond(),
+                       __METHOD__,
+                       $options
+               );
+       }
+
        /**
         * @return array
         */
index 9b8f5a6..a48d032 100644 (file)
@@ -40,9 +40,28 @@ use Wikimedia\AtEase\AtEase;
  * @ingroup HTTP
  */
 class WebRequest {
-       /** @var array */
+       /**
+        * The parameters from $_GET, $_POST and the path router
+        * @var array
+        */
        protected $data;
-       /** @var array */
+
+       /**
+        * The parameters from $_GET. The parameters from the path router are
+        * added by interpolateTitle() during Setup.php.
+        * @var array
+        */
+       protected $queryAndPathParams;
+
+       /**
+        * The parameters from $_GET only.
+        */
+       protected $queryParams;
+
+       /**
+        * Lazy-initialized request headers indexed by upper-case header name
+        * @var array
+        */
        protected $headers = [];
 
        /**
@@ -100,6 +119,8 @@ class WebRequest {
                // POST overrides GET data
                // We don't use $_REQUEST here to avoid interference from cookies...
                $this->data = $_POST + $_GET;
+
+               $this->queryAndPathParams = $this->queryParams = $_GET;
        }
 
        /**
@@ -162,8 +183,9 @@ class WebRequest {
                        }
 
                        global $wgActionPaths;
-                       if ( $wgActionPaths ) {
-                               $router->add( $wgActionPaths, [ 'action' => '$key' ] );
+                       $articlePaths = PathRouter::getActionPaths( $wgActionPaths, $wgArticlePath );
+                       if ( $articlePaths ) {
+                               $router->add( $articlePaths, [ 'action' => '$key' ] );
                        }
 
                        global $wgVariantArticlePath;
@@ -335,7 +357,7 @@ class WebRequest {
 
                $matches = self::getPathInfo( 'title' );
                foreach ( $matches as $key => $val ) {
-                       $this->data[$key] = $_GET[$key] = $_REQUEST[$key] = $val;
+                       $this->data[$key] = $this->queryAndPathParams[$key] = $val;
                }
        }
 
@@ -667,14 +689,27 @@ class WebRequest {
        }
 
        /**
-        * Get the values passed in the query string.
+        * Get the values passed in the query string and the path router parameters.
         * No transformation is performed on the values.
         *
         * @codeCoverageIgnore
         * @return array
         */
        public function getQueryValues() {
-               return $_GET;
+               return $this->queryAndPathParams;
+       }
+
+       /**
+        * Get the values passed in the query string only, not including the path
+        * router parameters. This is less suitable for self-links to index.php but
+        * useful for other entry points. No transformation is performed on the
+        * values.
+        *
+        * @since 1.34
+        * @return array
+        */
+       public function getQueryValuesOnly() {
+               return $this->queryParams;
        }
 
        /**
index 7bcfc88..207721e 100644 (file)
@@ -743,8 +743,6 @@ class InfoAction extends FormlessAction {
                        self::getCacheKey( $cache, $page->getTitle(), $page->getLatest() ),
                        WANObjectCache::TTL_WEEK,
                        function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname, $services ) {
-                               global $wgActorTableSchemaMigrationStage;
-
                                $title = $page->getTitle();
                                $id = $title->getArticleID();
 
@@ -752,19 +750,11 @@ class InfoAction extends FormlessAction {
                                $dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
                                $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
 
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                                       $tables = [ 'revision_actor_temp' ];
-                                       $field = 'revactor_actor';
-                                       $pageField = 'revactor_page';
-                                       $tsField = 'revactor_timestamp';
-                                       $joins = [];
-                               } else {
-                                       $tables = [ 'revision' ];
-                                       $field = 'rev_user_text';
-                                       $pageField = 'rev_page';
-                                       $tsField = 'rev_timestamp';
-                                       $joins = [];
-                               }
+                               $tables = [ 'revision_actor_temp' ];
+                               $field = 'revactor_actor';
+                               $pageField = 'revactor_page';
+                               $tsField = 'revactor_timestamp';
+                               $joins = [];
 
                                $watchedItemStore = $services->getWatchedItemStore();
 
index 0cd9806..056d10c 100644 (file)
@@ -992,7 +992,7 @@ abstract class ApiBase extends ContextSource {
                        return;
                }
 
-               $queryValues = $this->getRequest()->getQueryValues();
+               $queryValues = $this->getRequest()->getQueryValuesOnly();
                $badParams = [];
                foreach ( $params as $param ) {
                        if ( $prefix !== 'noprefix' ) {
index a5e7437..4b74a3d 100644 (file)
@@ -76,10 +76,6 @@ class ApiExpandTemplates extends ApiBase {
                                        $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
                                                wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
                                }
-                       } else {
-                               // Consider the title derived from the revid as having
-                               // been provided.
-                               $titleProvided = true;
                        }
                }
 
index a9fe258..7bbce97 100644 (file)
@@ -281,7 +281,10 @@ class ApiMain extends ApiBase {
                }
                $this->mResult->setErrorFormatter( $this->getErrorFormatter() );
 
-               $this->mModuleMgr = new ApiModuleManager( $this );
+               $this->mModuleMgr = new ApiModuleManager(
+                       $this,
+                       MediaWikiServices::getInstance()->getObjectFactory()
+               );
                $this->mModuleMgr->addModules( self::$Modules, 'action' );
                $this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' );
                $this->mModuleMgr->addModules( self::$Formats, 'format' );
@@ -293,7 +296,6 @@ class ApiMain extends ApiBase {
                $this->mEnableWrite = $enableWrite;
 
                $this->mCdnMaxAge = -1; // flag for executeActionWithErrorHandling()
-               $this->mCommit = false;
        }
 
        /**
index d2df013..8d5a82b 100644 (file)
@@ -21,6 +21,9 @@
  * @since 1.21
  */
 
+use MediaWiki\MediaWikiServices;
+use Wikimedia\ObjectFactory;
+
 /**
  * This class holds a list of modules and handles instantiation
  *
@@ -45,64 +48,35 @@ class ApiModuleManager extends ContextSource {
         * @var array[]
         */
        private $mModules = [];
+       /**
+        * @var ObjectFactory
+        */
+       private $objectFactory;
 
        /**
         * Construct new module manager
+        *
         * @param ApiBase $parentModule Parent module instance will be used during instantiation
+        * @param ObjectFactory|null $objectFactory Object factory to use when instantiating modules
         */
-       public function __construct( ApiBase $parentModule ) {
+       public function __construct( ApiBase $parentModule, ObjectFactory $objectFactory = null ) {
                $this->mParent = $parentModule;
+               $this->objectFactory = $objectFactory ?? MediaWikiServices::getInstance()->getObjectFactory();
        }
 
        /**
         * Add a list of modules to the manager. Each module is described
-        * by a module spec.
-        *
-        * Each module spec is an associative array containing at least
-        * the 'class' key for the module's class, and optionally a
-        * 'factory' key for the factory function to use for the module.
+        * by an ObjectFactory spec.
         *
-        * That factory function will be called with two parameters,
-        * the parent module (an instance of ApiBase, usually ApiMain)
-        * and the name the module was registered under. The return
-        * value must be an instance of the class given in the 'class'
-        * field.
+        * This simply calls `addModule()` for each module in `$modules`.
         *
-        * For backward compatibility, the module spec may also be a
-        * simple string containing the module's class name. In that
-        * case, the class' constructor will be called with the parent
-        * module and module name as parameters, as described above.
-        *
-        * Examples for defining module specs:
-        *
-        * @code
-        *  $modules['foo'] = 'ApiFoo';
-        *  $modules['bar'] = [
-        *      'class' => ApiBar::class,
-        *      'factory' => function( $main, $name ) { ... }
-        *  ];
-        *  $modules['xyzzy'] = [
-        *      'class' => ApiXyzzy::class,
-        *      'factory' => [ XyzzyFactory::class, 'newApiModule' ]
-        *  ];
-        * @endcode
-        *
-        * @param array $modules A map of ModuleName => ModuleSpec; The ModuleSpec
-        *        is either a string containing the module's class name, or an associative
-        *        array (see above for details).
+        * @see ApiModuleManager::addModule()
+        * @param array $modules A map of ModuleName => ModuleSpec
         * @param string $group Which group modules belong to (action,format,...)
         */
        public function addModules( array $modules, $group ) {
                foreach ( $modules as $name => $moduleSpec ) {
-                       if ( is_array( $moduleSpec ) ) {
-                               $class = $moduleSpec['class'];
-                               $factory = ( $moduleSpec['factory'] ?? null );
-                       } else {
-                               $class = $moduleSpec;
-                               $factory = null;
-                       }
-
-                       $this->addModule( $name, $group, $class, $factory );
+                       $this->addModule( $name, $group, $moduleSpec );
                }
        }
 
@@ -111,14 +85,21 @@ class ApiModuleManager extends ContextSource {
         * classes who wish to add their own modules to their lexicon or override the
         * behavior of inherent ones.
         *
+        * ObjectFactory is used to instantiate the module when needed. The parent module
+        * (`$parentModule` from `__construct()`) and the `$name` are passed as extraArgs.
+        *
+        * @since 1.34, accepts an ObjectFactory spec as the third parameter. The old calling convention,
+        *  passing a class name as parameter #3 and an optional factory callable as parameter #4, is
+        *  deprecated.
         * @param string $name The identifier for this module.
         * @param string $group Name of the module group
-        * @param string $class The class where this module is implemented.
-        * @param callable|null $factory Callback for instantiating the module.
+        * @param string|array $spec The ObjectFactory spec for instantiating the module,
+        *  or a class name to instantiate.
+        * @param callable|null $factory Callback for instantiating the module (deprecated).
         *
         * @throws InvalidArgumentException
         */
-       public function addModule( $name, $group, $class, $factory = null ) {
+       public function addModule( $name, $group, $spec, $factory = null ) {
                if ( !is_string( $name ) ) {
                        throw new InvalidArgumentException( '$name must be a string' );
                }
@@ -127,16 +108,23 @@ class ApiModuleManager extends ContextSource {
                        throw new InvalidArgumentException( '$group must be a string' );
                }
 
-               if ( !is_string( $class ) ) {
-                       throw new InvalidArgumentException( '$class must be a string' );
-               }
+               if ( is_string( $spec ) ) {
+                       $spec = [
+                               'class' => $spec
+                       ];
 
-               if ( $factory !== null && !is_callable( $factory ) ) {
-                       throw new InvalidArgumentException( '$factory must be a callable (or null)' );
+                       if ( is_callable( $factory ) ) {
+                               wfDeprecated( __METHOD__ . ' with $class and $factory', '1.34' );
+                               $spec['factory'] = $factory;
+                       }
+               } elseif ( !is_array( $spec ) ) {
+                       throw new InvalidArgumentException( '$spec must be a string or an array' );
+               } elseif ( !isset( $spec['class'] ) ) {
+                       throw new InvalidArgumentException( '$spec must define a class name' );
                }
 
                $this->mGroups[$group] = null;
-               $this->mModules[$name] = [ $group, $class, $factory ];
+               $this->mModules[$name] = [ $group, $spec ];
        }
 
        /**
@@ -153,7 +141,7 @@ class ApiModuleManager extends ContextSource {
                        return null;
                }
 
-               list( $moduleGroup, $moduleClass, $moduleFactory ) = $this->mModules[$moduleName];
+               list( $moduleGroup, $spec ) = $this->mModules[$moduleName];
 
                if ( $group !== null && $moduleGroup !== $group ) {
                        return null;
@@ -164,7 +152,7 @@ class ApiModuleManager extends ContextSource {
                        return $this->mInstances[$moduleName];
                } else {
                        // new instance
-                       $instance = $this->instantiateModule( $moduleName, $moduleClass, $moduleFactory );
+                       $instance = $this->instantiateModule( $moduleName, $spec );
 
                        if ( !$ignoreCache ) {
                                // cache this instance in case it is needed later
@@ -179,28 +167,22 @@ class ApiModuleManager extends ContextSource {
         * Instantiate the module using the given class or factory function.
         *
         * @param string $name The identifier for this module.
-        * @param string $class The class where this module is implemented.
-        * @param callable|null $factory Callback for instantiating the module.
+        * @param array $spec The ObjectFactory spec for instantiating the module.
         *
-        * @throws MWException
+        * @throws UnexpectedValueException
         * @return ApiBase
         */
-       private function instantiateModule( $name, $class, $factory = null ) {
-               if ( $factory !== null ) {
-                       // create instance from factory
-                       $instance = call_user_func( $factory, $this->mParent, $name );
-
-                       if ( !$instance instanceof $class ) {
-                               throw new MWException(
-                                       "The factory function for module $name did not return an instance of $class!"
-                               );
-                       }
-               } else {
-                       // create instance from class name
-                       $instance = new $class( $this->mParent, $name );
-               }
-
-               return $instance;
+       private function instantiateModule( $name, $spec ) {
+               return $this->objectFactory->createObject(
+                       $spec,
+                       [
+                               'extraArgs' => [
+                                       $this->mParent,
+                                       $name
+                               ],
+                               'assertClass' => $spec['class']
+                       ]
+               );
        }
 
        /**
@@ -213,8 +195,8 @@ class ApiModuleManager extends ContextSource {
                        return array_keys( $this->mModules );
                }
                $result = [];
-               foreach ( $this->mModules as $name => $grpCls ) {
-                       if ( $grpCls[0] === $group ) {
+               foreach ( $this->mModules as $name => $groupAndSpec ) {
+                       if ( $groupAndSpec[0] === $group ) {
                                $result[] = $name;
                        }
                }
@@ -229,9 +211,9 @@ class ApiModuleManager extends ContextSource {
         */
        public function getNamesWithClasses( $group = null ) {
                $result = [];
-               foreach ( $this->mModules as $name => $grpCls ) {
-                       if ( $group === null || $grpCls[0] === $group ) {
-                               $result[$name] = $grpCls[1];
+               foreach ( $this->mModules as $name => $groupAndSpec ) {
+                       if ( $group === null || $groupAndSpec[0] === $group ) {
+                               $result[$name] = $groupAndSpec[1]['class'];
                        }
                }
 
@@ -247,7 +229,7 @@ class ApiModuleManager extends ContextSource {
         */
        public function getClassName( $module ) {
                if ( isset( $this->mModules[$module] ) ) {
-                       return $this->mModules[$module][1];
+                       return $this->mModules[$module][1]['class'];
                }
 
                return false;
index 6afb018..e68676a 100644 (file)
@@ -1166,7 +1166,8 @@ class ApiPageSet extends ApiBase {
                $services = MediaWikiServices::getInstance();
                $contLang = $services->getContentLanguage();
 
-               foreach ( $titles as $title ) {
+               $titleObjects = [];
+               foreach ( $titles as $index => $title ) {
                        if ( is_string( $title ) ) {
                                try {
                                        $titleObj = Title::newFromTextThrow( $title, $this->mDefaultNamespace );
@@ -1185,6 +1186,16 @@ class ApiPageSet extends ApiBase {
                        } else {
                                $titleObj = $title;
                        }
+
+                       $titleObjects[$index] = $titleObj;
+               }
+
+               // Get gender information
+               $genderCache = $services->getGenderCache();
+               $genderCache->doTitlesArray( $titleObjects, __METHOD__ );
+
+               foreach ( $titleObjects as $index => $titleObj ) {
+                       $title = is_string( $titles[$index] ) ? $titles[$index] : false;
                        $unconvertedTitle = $titleObj->getPrefixedText();
                        $titleWasConverted = false;
                        if ( $titleObj->isExternal() ) {
@@ -1197,7 +1208,7 @@ class ApiPageSet extends ApiBase {
                                ) {
                                        // Language::findVariantLink will modify titleText and titleObj into
                                        // the canonical variant if possible
-                                       $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
+                                       $titleText = $title !== false ? $title : $titleObj->getPrefixedText();
                                        $contLang->findVariantLink( $titleText, $titleObj );
                                        $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
                                }
@@ -1245,24 +1256,13 @@ class ApiPageSet extends ApiBase {
                        if ( $titleWasConverted ) {
                                $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
                                // In this case the page can't be Special.
-                               if ( is_string( $title ) && $title !== $unconvertedTitle ) {
+                               if ( $title !== false && $title !== $unconvertedTitle ) {
                                        $this->mNormalizedTitles[$title] = $unconvertedTitle;
                                }
-                       } elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
+                       } elseif ( $title !== false && $title !== $titleObj->getPrefixedText() ) {
                                $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
                        }
-
-                       // Need gender information
-                       if (
-                               $services->getNamespaceInfo()->
-                                       hasGenderDistinction( $titleObj->getNamespace() )
-                       ) {
-                               $usernames[] = $titleObj->getText();
-                       }
                }
-               // Get gender information
-               $genderCache = $services->getGenderCache();
-               $genderCache->doQuery( $usernames, __METHOD__ );
 
                return $linkBatch;
        }
index c78e445..a7ff729 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
 
 /**
@@ -134,7 +135,10 @@ class ApiQuery extends ApiBase {
        public function __construct( ApiMain $main, $action ) {
                parent::__construct( $main, $action );
 
-               $this->mModuleMgr = new ApiModuleManager( $this );
+               $this->mModuleMgr = new ApiModuleManager(
+                       $this,
+                       MediaWikiServices::getInstance()->getObjectFactory()
+               );
 
                // Allow custom modules to be added in LocalSettings.php
                $config = $this->getConfig();
index 7d6d342..2a49984 100644 (file)
@@ -43,9 +43,6 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
         * @return void
         */
        protected function run( ApiPageSet $resultPageSet = null ) {
-               // Before doing anything at all, let's check permissions
-               $this->checkUserRightsAny( 'deletedhistory' );
-
                $user = $this->getUser();
                $db = $this->getDB();
                $params = $this->extractRequestParams( false );
@@ -144,8 +141,15 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                }
 
                // This means stricter restrictions
-               if ( $this->fetchContent ) {
-                       $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
+               if ( ( $this->fld_comment || $this->fld_parsedcomment ) &&
+                       !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' )
+               ) {
+                       $this->dieWithError( 'apierror-cantview-deleted-comment', 'permissiondenied' );
+               }
+               if ( $this->fetchContent &&
+                       !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' )
+               ) {
+                       $this->dieWithError( 'apierror-cantview-deleted-revision-content', 'permissiondenied' );
                }
 
                $miser_ns = null;
@@ -235,8 +239,6 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
 
                if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
                        // Paranoia: avoid brute force searches (T19342)
-                       // (shouldn't be able to get here without 'deletedhistory', but
-                       // check it again just in case)
                        if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
                        } elseif ( !$this->getPermissionManager()
index 3751102..3d4c49b 100644 (file)
@@ -40,8 +40,6 @@ class ApiQueryAllRevisions extends ApiQueryRevisionsBase {
         * @return void
         */
        protected function run( ApiPageSet $resultPageSet = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $db = $this->getDB();
                $params = $this->extractRequestParams( false );
                $services = MediaWikiServices::getInstance();
@@ -54,9 +52,7 @@ class ApiQueryAllRevisions extends ApiQueryRevisionsBase {
                $tsField = 'rev_timestamp';
                $idField = 'rev_id';
                $pageField = 'rev_page';
-               if ( $params['user'] !== null &&
-                       ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
-               ) {
+               if ( $params['user'] !== null ) {
                        // The query is probably best done using the actor_timestamp index on
                        // revision_actor_temp. Use the denormalized fields from that table.
                        $tsField = 'revactor_timestamp';
index e0513e2..a7d4fb9 100644 (file)
@@ -41,8 +41,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
        }
 
        public function execute() {
-               global $wgActorTableSchemaMigrationStage;
-
                $params = $this->extractRequestParams();
                $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
 
@@ -181,22 +179,17 @@ class ApiQueryAllUsers extends ApiQueryBase {
                        ] ] );
 
                        // Actually count the actions using a subquery (T66505 and T66507)
-                       $tables = [ 'recentchanges' ];
-                       $joins = [];
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_OLD ) {
-                               $userCond = 'rc_user_text = user_name';
-                       } else {
-                               $tables[] = 'actor';
-                               $joins['actor'] = [ 'JOIN', 'rc_actor = actor_id' ];
-                               $userCond = 'actor_user = user_id';
-                       }
+                       $tables = [ 'recentchanges', 'actor' ];
+                       $joins = [
+                               'actor' => [ 'JOIN', 'rc_actor = actor_id' ],
+                       ];
                        $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
                        $this->addFields( [
                                'recentactions' => '(' . $db->selectSQLText(
                                        $tables,
                                        'COUNT(*)',
                                        [
-                                               $userCond,
+                                               'actor_user = user_id',
                                                'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
                                                'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
                                                'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
index a1945c4..f2e306f 100644 (file)
@@ -46,8 +46,6 @@ class ApiQueryContributors extends ApiQueryBase {
        }
 
        public function execute() {
-               global $wgActorTableSchemaMigrationStage;
-
                $db = $this->getDB();
                $params = $this->extractRequestParams();
                $this->requireMaxOneParameter( $params, 'group', 'excludegroup', 'rights', 'excluderights' );
@@ -80,14 +78,10 @@ class ApiQueryContributors extends ApiQueryBase {
                $result = $this->getResult();
                $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
 
-               // For SCHEMA_COMPAT_READ_NEW, target indexes on the
-               // revision_actor_temp table, otherwise on the revision table.
-               $pageField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
-                       ? 'revactor_page' : 'rev_page';
-               $idField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
-                       ? 'revactor_actor' : $revQuery['fields']['rev_user'];
-               $countField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
-                       ? 'revactor_actor' : $revQuery['fields']['rev_user_text'];
+               // Target indexes on the revision_actor_temp table.
+               $pageField = 'revactor_page';
+               $idField = 'revactor_actor';
+               $countField = 'revactor_actor';
 
                // First, count anons
                $this->addTables( $revQuery['tables'] );
index fc88499..12fd20a 100644 (file)
@@ -40,8 +40,6 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
 
        protected function run( ApiPageSet $resultPageSet = null ) {
                $user = $this->getUser();
-               // Before doing anything at all, let's check permissions
-               $this->checkUserRightsAny( 'deletedhistory' );
 
                $pageSet = $this->getPageSet();
                $pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
@@ -95,8 +93,15 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
                }
 
                // This means stricter restrictions
-               if ( $this->fetchContent ) {
-                       $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
+               if ( ( $this->fld_comment || $this->fld_parsedcomment ) &&
+                       !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' )
+               ) {
+                       $this->dieWithError( 'apierror-cantview-deleted-comment', 'permissiondenied' );
+               }
+               if ( $this->fetchContent &&
+                       !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' )
+               ) {
+                       $this->dieWithError( 'apierror-cantview-deleted-revision-content', 'permissiondenied' );
                }
 
                $dir = $params['dir'];
@@ -130,8 +135,6 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
 
                if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
                        // Paranoia: avoid brute force searches (T19342)
-                       // (shouldn't be able to get here without 'deletedhistory', but
-                       // check it again just in case)
                        if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
                                $bitmask = RevisionRecord::DELETED_USER;
                        } elseif ( !$this->getPermissionManager()
index f9087eb..c84f457 100644 (file)
@@ -38,9 +38,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
        }
 
        public function execute() {
-               // Before doing anything at all, let's check permissions
-               $this->checkUserRightsAny( 'deletedhistory' );
-
                $user = $this->getUser();
                $db = $this->getDB();
                $commentStore = CommentStore::getStore();
@@ -60,6 +57,17 @@ class ApiQueryFilearchive extends ApiQueryBase {
                $fld_bitdepth = isset( $prop['bitdepth'] );
                $fld_archivename = isset( $prop['archivename'] );
 
+               if ( $fld_description &&
+                       !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' )
+               ) {
+                       $this->dieWithError( 'apierror-cantview-deleted-description', 'permissiondenied' );
+               }
+               if ( $fld_metadata &&
+                       !$this->getPermissionManager()->userHasAnyRight( $user, 'deletedtext', 'undelete' )
+               ) {
+                       $this->dieWithError( 'apierror-cantview-deleted-metadata', 'permissiondenied' );
+               }
+
                $fileQuery = ArchivedFile::getQueryInfo();
                $this->addTables( $fileQuery['tables'] );
                $this->addFields( $fileQuery['fields'] );
@@ -110,23 +118,22 @@ class ApiQueryFilearchive extends ApiQueryBase {
                        }
                        if ( $sha1 ) {
                                $this->addWhereFld( 'fa_sha1', $sha1 );
+                               // Paranoia: avoid brute force searches (T19342)
+                               if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) {
+                                       $bitmask = File::DELETED_FILE;
+                               } elseif ( !$this->getPermissionManager()
+                                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
+                               ) {
+                                       $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
+                               } else {
+                                       $bitmask = 0;
+                               }
+                               if ( $bitmask ) {
+                                       $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
+                               }
                        }
                }
 
-               // Exclude files this user can't view.
-               if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedtext' ) ) {
-                       $bitmask = File::DELETED_FILE;
-               } elseif ( !$this->getPermissionManager()
-                       ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
-               ) {
-                       $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
-               } else {
-                       $bitmask = 0;
-               }
-               if ( $bitmask ) {
-                       $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
-               }
-
                $limit = $params['limit'];
                $this->addOption( 'LIMIT', $limit + 1 );
                $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
@@ -150,6 +157,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
                                break;
                        }
 
+                       $canViewFile = RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_FILE, $user );
+
                        $file = [];
                        $file['id'] = (int)$row->fa_id;
                        $file['name'] = $row->fa_name;
@@ -171,13 +180,13 @@ class ApiQueryFilearchive extends ApiQueryBase {
                                $file['userid'] = (int)$row->fa_user;
                                $file['user'] = $row->fa_user_text;
                        }
-                       if ( $fld_sha1 ) {
+                       if ( $fld_sha1 && $canViewFile ) {
                                $file['sha1'] = Wikimedia\base_convert( $row->fa_sha1, 36, 16, 40 );
                        }
                        if ( $fld_timestamp ) {
                                $file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
                        }
-                       if ( $fld_size || $fld_dimensions ) {
+                       if ( ( $fld_size || $fld_dimensions ) && $canViewFile ) {
                                $file['size'] = $row->fa_size;
 
                                $pageCount = ArchivedFile::newFromRow( $row )->pageCount();
@@ -188,18 +197,18 @@ class ApiQueryFilearchive extends ApiQueryBase {
                                $file['height'] = $row->fa_height;
                                $file['width'] = $row->fa_width;
                        }
-                       if ( $fld_mediatype ) {
+                       if ( $fld_mediatype && $canViewFile ) {
                                $file['mediatype'] = $row->fa_media_type;
                        }
-                       if ( $fld_metadata ) {
+                       if ( $fld_metadata && $canViewFile ) {
                                $file['metadata'] = $row->fa_metadata
                                        ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
                                        : null;
                        }
-                       if ( $fld_bitdepth ) {
+                       if ( $fld_bitdepth && $canViewFile ) {
                                $file['bitdepth'] = $row->fa_bits;
                        }
-                       if ( $fld_mime ) {
+                       if ( $fld_mime && $canViewFile ) {
                                $file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
                        }
                        if ( $fld_archivename && !is_null( $row->fa_archive_name ) ) {
index d616ad4..5b2b1b7 100644 (file)
@@ -85,8 +85,6 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
        }
 
        protected function run( ApiPageSet $resultPageSet = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $params = $this->extractRequestParams( false );
                $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
 
@@ -137,9 +135,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                $idField = 'rev_id';
                $tsField = 'rev_timestamp';
                $pageField = 'rev_page';
-               if ( $params['user'] !== null &&
-                       ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
-               ) {
+               if ( $params['user'] !== null ) {
                        // We're going to want to use the page_actor_timestamp index (on revision_actor_temp)
                        // so use that table's denormalized fields.
                        $idField = 'revactor_rev';
index 1924ca0..c84de8c 100644 (file)
@@ -70,11 +70,37 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
                }
        }
 
-       private $propertyFilter = [
+       private static $propertyFilter = [
                'user', 'userid', 'comment', 'parsedcomment',
                'mediatype', 'archivename', 'uploadwarning',
        ];
 
+       /**
+        * Returns all possible parameters to siiprop
+        *
+        * @param array|null $filter List of properties to filter out
+        * @return array
+        */
+       public static function getPropertyNames( $filter = null ) {
+               if ( $filter === null ) {
+                       $filter = self::$propertyFilter;
+               }
+               return parent::getPropertyNames( $filter );
+       }
+
+       /**
+        * Returns messages for all possible parameters to siiprop
+        *
+        * @param array|null $filter List of properties to filter out
+        * @return array
+        */
+       public static function getPropertyMessages( $filter = null ) {
+               if ( $filter === null ) {
+                       $filter = self::$propertyFilter;
+               }
+               return parent::getPropertyMessages( $filter );
+       }
+
        public function getAllowedParams() {
                return [
                        'filekey' => [
@@ -87,9 +113,9 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
                        'prop' => [
                                ApiBase::PARAM_ISMULTI => true,
                                ApiBase::PARAM_DFLT => 'timestamp|url',
-                               ApiBase::PARAM_TYPE => self::getPropertyNames( $this->propertyFilter ),
+                               ApiBase::PARAM_TYPE => self::getPropertyNames(),
                                ApiBase::PARAM_HELP_MSG => 'apihelp-query+imageinfo-param-prop',
-                               ApiBase::PARAM_HELP_MSG_PER_VALUE => self::getPropertyMessages( $this->propertyFilter )
+                               ApiBase::PARAM_HELP_MSG_PER_VALUE => self::getPropertyMessages()
                        ],
                        'urlwidth' => [
                                ApiBase::PARAM_TYPE => 'integer',
index 919c763..189f957 100644 (file)
@@ -42,8 +42,6 @@ class ApiQueryUserContribs extends ApiQueryBase {
                $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
 
        public function execute() {
-               global $wgActorTableSchemaMigrationStage;
-
                // Parse some parameters
                $this->params = $this->extractRequestParams();
 
@@ -82,8 +80,6 @@ class ApiQueryUserContribs extends ApiQueryBase {
                        // a wiki with users "Test00000001" to "Test99999999"), use a
                        // generator with batched lookup and continuation.
                        $userIter = call_user_func( function () use ( $dbSecondary, $sort, $op, $fname ) {
-                               global $wgActorTableSchemaMigrationStage;
-
                                $fromName = false;
                                if ( !is_null( $this->params['continue'] ) ) {
                                        $continue = explode( '|', $this->params['continue'] );
@@ -97,26 +93,13 @@ class ApiQueryUserContribs extends ApiQueryBase {
 
                                do {
                                        $from = $fromName ? "$op= " . $dbSecondary->addQuotes( $fromName ) : false;
-
-                                       // For the new schema, pull from the actor table. For the
-                                       // old, pull from rev_user.
-                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                                               $res = $dbSecondary->select(
-                                                       'actor',
-                                                       [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
-                                                       array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
-                                                       $fname,
-                                                       [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
-                                               );
-                                       } else {
-                                               $res = $dbSecondary->select(
-                                                       'revision',
-                                                       [ 'actor_id' => 'NULL', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
-                                                       array_merge( [ "rev_user_text$like" ], $from ? [ "rev_user_text $from" ] : [] ),
-                                                       $fname,
-                                                       [ 'DISTINCT', 'ORDER BY' => [ "rev_user_text $sort" ], 'LIMIT' => $limit ]
-                                               );
-                                       }
+                                       $res = $dbSecondary->select(
+                                               'actor',
+                                               [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
+                                               array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
+                                               $fname,
+                                               [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
+                                       );
 
                                        $count = 0;
                                        $fromName = false;
@@ -159,25 +142,13 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                $from = "$op= $fromId";
                        }
 
-                       // For the new schema, just select from the actor table. For the
-                       // old, select from user.
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                               $res = $dbSecondary->select(
-                                       'actor',
-                                       [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
-                                       array_merge( [ 'actor_user' => $ids ], $from ? [ "actor_id $from" ] : [] ),
-                                       __METHOD__,
-                                       [ 'ORDER BY' => "user_id $sort" ]
-                               );
-                       } else {
-                               $res = $dbSecondary->select(
-                                       'user',
-                                       [ 'actor_id' => 'NULL', 'user_id' => 'user_id', 'user_name' => 'user_name' ],
-                                       array_merge( [ 'user_id' => $ids ], $from ? [ "user_id $from" ] : [] ),
-                                       __METHOD__,
-                                       [ 'ORDER BY' => "user_id $sort" ]
-                               );
-                       }
+                       $res = $dbSecondary->select(
+                               'actor',
+                               [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
+                               array_merge( [ 'actor_user' => $ids ], $from ? [ "actor_id $from" ] : [] ),
+                               __METHOD__,
+                               [ 'ORDER BY' => "user_id $sort" ]
+                       );
                        $userIter = UserArray::newFromResult( $res );
                        $batchSize = count( $ids );
                } else {
@@ -222,57 +193,22 @@ class ApiQueryUserContribs extends ApiQueryBase {
                                $from = "$op= " . $dbSecondary->addQuotes( $fromName );
                        }
 
-                       // For the new schema, just select from the actor table. For the
-                       // old, select from user then merge in any unknown users (IPs and imports).
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                               $res = $dbSecondary->select(
-                                       'actor',
-                                       [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
-                                       array_merge( [ 'actor_name' => array_keys( $names ) ], $from ? [ "actor_id $from" ] : [] ),
-                                       __METHOD__,
-                                       [ 'ORDER BY' => "actor_name $sort" ]
-                               );
-                               $userIter = UserArray::newFromResult( $res );
-                       } else {
-                               $res = $dbSecondary->select(
-                                       'user',
-                                       [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
-                                       array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       $names[$row->user_name] = $row;
-                               }
-                               if ( $this->params['dir'] == 'newer' ) {
-                                       ksort( $names, SORT_STRING );
-                               } else {
-                                       krsort( $names, SORT_STRING );
-                               }
-                               $neg = $op === '>' ? -1 : 1;
-                               $userIter = call_user_func( function () use ( $names, $fromName, $neg ) {
-                                       foreach ( $names as $name => $row ) {
-                                               if ( $fromName === false || $neg * strcmp( $name, $fromName ) <= 0 ) {
-                                                       $user = $row ? User::newFromRow( $row ) : User::newFromName( $name, false );
-                                                       yield $user;
-                                               }
-                                       }
-                               } );
-                       }
+                       $res = $dbSecondary->select(
+                               'actor',
+                               [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
+                               array_merge( [ 'actor_name' => array_keys( $names ) ], $from ? [ "actor_id $from" ] : [] ),
+                               __METHOD__,
+                               [ 'ORDER BY' => "actor_name $sort" ]
+                       );
+                       $userIter = UserArray::newFromResult( $res );
                        $batchSize = count( $names );
                }
 
-               // With the new schema, the DB query will order by actor so update $this->orderBy to match.
-               if ( $batchSize > 1 && ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
+               // The DB query will order by actor so update $this->orderBy to match.
+               if ( $batchSize > 1 ) {
                        $this->orderBy = 'actor';
                }
 
-               // Use the 'contributions' replica, but only if we're querying by user ID (T216656).
-               if ( $this->orderBy === 'id' &&
-                       !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
-               ) {
-                       $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
-               }
-
                $count = 0;
                $limit = $this->params['limit'];
                $userIter->rewind();
@@ -325,47 +261,33 @@ class ApiQueryUserContribs extends ApiQueryBase {
         * @param int $limit
         */
        private function prepareQuery( array $users, $limit ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $this->resetQueryParams();
                $db = $this->getDB();
 
                $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [ 'page' ] );
 
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
-                       $orderUserField = 'rev_actor';
-                       $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
-                       $tsField = 'revactor_timestamp';
-                       $idField = 'revactor_rev';
-
-                       // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor`
-                       // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows
-                       // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves
-                       // because as generated by RevisionStore it'll have `revision` first rather than
-                       // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as
-                       // helped in that case, and not when there's only one User because in that case it fetches
-                       // the one `actor` row as a constant and doesn't filesort.
-                       if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) {
-                               $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user'];
-                               unset( $revQuery['joins']['temp_rev_user'] );
-                               $this->addOption( 'STRAIGHT_JOIN' );
-                               // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
-                               // when join conditions are given for all joins, but Gergő is wary of relying on that so pull
-                               // `revision_actor_temp` to the start.
-                               $revQuery['tables'] =
-                                       [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables'];
-                       }
-               } else {
-                       // If we're dealing with user names (rather than IDs) in read-old mode,
-                       // pass false for ActorMigration::getWhere()'s $useId parameter so
-                       // $revWhere['conds'] isn't an OR.
-                       $revWhere = ActorMigration::newMigration()
-                               ->getWhere( $db, 'rev_user', $users, $this->orderBy === 'id' );
-                       $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
-                       $userField = $revQuery['fields'][$orderUserField];
-                       $tsField = 'rev_timestamp';
-                       $idField = 'rev_id';
+               $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
+               $orderUserField = 'rev_actor';
+               $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
+               $tsField = 'revactor_timestamp';
+               $idField = 'revactor_rev';
+
+               // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor`
+               // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows
+               // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves
+               // because as generated by RevisionStore it'll have `revision` first rather than
+               // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as
+               // helped in that case, and not when there's only one User because in that case it fetches
+               // the one `actor` row as a constant and doesn't filesort.
+               if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) {
+                       $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user'];
+                       unset( $revQuery['joins']['temp_rev_user'] );
+                       $this->addOption( 'STRAIGHT_JOIN' );
+                       // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
+                       // when join conditions are given for all joins, but Gergő is wary of relying on that so pull
+                       // `revision_actor_temp` to the start.
+                       $revQuery['tables'] =
+                               [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables'];
                }
 
                $this->addTables( $revQuery['tables'] );
index 12d7435..28dea3b 100644 (file)
@@ -303,32 +303,17 @@ class ApiQueryUserInfo extends ApiQueryBase {
         * @return string|null ISO 8601 timestamp of current user's last contribution or null if none
         */
        protected function getLatestContributionTime() {
-               global $wgActorTableSchemaMigrationStage;
-
                $user = $this->getUser();
                $dbr = $this->getDB();
 
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       if ( $user->getActorId() === null ) {
-                               return null;
-                       }
-                       $res = $dbr->selectField( 'revision_actor_temp',
-                               'MAX(revactor_timestamp)',
-                               [ 'revactor_actor' => $user->getActorId() ],
-                               __METHOD__
-                       );
-               } else {
-                       if ( $user->isLoggedIn() ) {
-                               $conds = [ 'rev_user' => $user->getId() ];
-                       } else {
-                               $conds = [ 'rev_user_text' => $user->getName() ];
-                       }
-                       $res = $dbr->selectField( 'revision',
-                               'MAX(rev_timestamp)',
-                               $conds,
-                               __METHOD__
-                       );
+               if ( $user->getActorId() === null ) {
+                       return null;
                }
+               $res = $dbr->selectField( 'revision_actor_temp',
+                       'MAX(revactor_timestamp)',
+                       [ 'revactor_actor' => $user->getActorId() ],
+                       __METHOD__
+               );
 
                return $res ? wfTimestamp( TS_ISO_8601, $res ) : null;
        }
index d2bbe7b..7e9f56d 100644 (file)
@@ -93,7 +93,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
                        $titles = $pageSet->getGoodTitles();
                        $title = reset( $titles );
                        if ( $title ) {
-                               $revid = $title->getNextRevisionID( $params['newerthanrevid'], Title::GAID_FOR_UPDATE );
+                               $revid = $title->getNextRevisionID( $params['newerthanrevid'], Title::READ_LATEST );
                                if ( $revid ) {
                                        $timestamp = $dbw->timestamp(
                                                MediaWikiServices::getInstance()->getRevisionStore()->getTimestampFromId( $title, $revid )
index aff0183..bb6c580 100644 (file)
@@ -20,7 +20,6 @@
  */
 
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Revision\RevisionStore;
 
 /**
  * @ingroup API
@@ -28,7 +27,9 @@ use MediaWiki\Revision\RevisionStore;
  */
 class ApiTag extends ApiBase {
 
-       /** @var RevisionStore */
+       use ApiBlockInfoTrait;
+
+       /** @var \MediaWiki\Revision\RevisionStore */
        private $revisionStore;
 
        public function execute() {
@@ -40,9 +41,9 @@ class ApiTag extends ApiBase {
                // make sure the user is allowed
                $this->checkUserRightsAny( 'changetags' );
 
-               // @TODO Use PermissionManager::isBlockedFrom() instead.
+               // Fail early if the user is sitewide blocked.
                $block = $user->getBlock();
-               if ( $block ) {
+               if ( $block && $block->isSitewide() ) {
                        $this->dieBlocked( $block );
                }
 
@@ -85,6 +86,7 @@ class ApiTag extends ApiBase {
        }
 
        protected function processIndividual( $type, $params, $id ) {
+               $user = $this->getUser();
                $idResult = [ $type => $id ];
 
                // validate the ID
@@ -92,9 +94,30 @@ class ApiTag extends ApiBase {
                switch ( $type ) {
                        case 'rcid':
                                $valid = RecentChange::newFromId( $id );
+                               if ( $valid && $this->getPermissionManager()->isBlockedFrom( $user, $valid->getTitle() ) ) {
+                                       $idResult['status'] = 'error';
+                                       $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
+                                               'apierror-blocked',
+                                               'blocked',
+                                               [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
+                                       ) );
+                                       return $idResult;
+                               }
                                break;
                        case 'revid':
                                $valid = $this->revisionStore->getRevisionById( $id );
+                               if (
+                                       $valid &&
+                                       $this->getPermissionManager()->isBlockedFrom( $user, $valid->getPageAsLinkTarget() )
+                               ) {
+                                       $idResult['status'] = 'error';
+                                       $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
+                                                       'apierror-blocked',
+                                                       'blocked',
+                                                       [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
+                                       ) );
+                                       return $idResult;
+                               }
                                break;
                        case 'logid':
                                $valid = self::validateLogId( $id );
index dc9c516..09d9c46 100644 (file)
        "apierror-cantoverwrite-sharedfile": "الملف الهدف موجود في مستودع مشترك وليست لديك صلاحية لتجاوزه.",
        "apierror-cantsend": "لم تقم بتسجيل الدخول أو ليس لديك عنوان بريد إلكتروني مؤكد أو غير مسموح لك بإرسال بريد إلكتروني إلى مستخدمين آخرين; لذلك لا يمكنك إرسال بريد إلكتروني.",
        "apierror-cantundelete": "تعذر الاسترجاع: قد لا تكون المراجعات المطلوبة موجودة، أو ربما تم الاسترجاع بالفعل.",
+       "apierror-cantview-deleted-description": "ليست لديك صلايبة لعرض أوصاف الملفات المحذوفة.",
+       "apierror-cantview-deleted-metadata": "ليست لديك صلايبة لعرض البيانات الوصفية للملفات المحذوفة.",
        "apierror-changeauth-norequest": "فشل في إنشاء طلب التغيير.",
        "apierror-chunk-too-small": "الحد الأدنى لحجم القطعة هو $1 {{PLURAL:$1|بايت}} للقطع غير النهائية.",
        "apierror-cidrtoobroad": "لا يُقبَل مدى $1 CIDR أكبر من /$2.",
index 8b42a07..cc5e872 100644 (file)
        "apierror-cantoverwrite-sharedfile": "The target file exists on a shared repository and you do not have permission to override it.",
        "apierror-cantsend": "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email.",
        "apierror-cantundelete": "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already.",
+       "apierror-cantview-deleted-comment": "You don't have permission to view deleted comments.",
+       "apierror-cantview-deleted-description": "You don't have permission to view descriptions of deleted files.",
+       "apierror-cantview-deleted-metadata": "You don't have permission to view metadata of deleted files.",
+       "apierror-cantview-deleted-revision-content": "You don't have permission to view content of deleted revisions.",
        "apierror-changeauth-norequest": "Failed to create change request.",
        "apierror-chunk-too-small": "Minimum chunk size is $1 {{PLURAL:$1|byte|bytes}} for non-final chunks.",
        "apierror-cidrtoobroad": "$1 CIDR ranges broader than /$2 are not accepted.",
index b72d519..7c5d2b5 100644 (file)
                        "Lucas Werkmeister (WMDE)"
                ]
        },
-       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MediaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes à l’API, voyez [[Special:ApiSandbox]].</p>",
+       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MediaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes à l’API, voyez [[Special:ApiSandbox]].</p>",
        "apihelp-main-param-action": "Quelle action effectuer.",
        "apihelp-main-param-format": "Le format de sortie.",
-       "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MédiaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manuel: paramètre Maxlag]] pour plus d’information.",
+       "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MediaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manuel: paramètre Maxlag]] pour plus d’information.",
        "apihelp-main-param-smaxage": "Fixer l’entête HTTP de contrôle de cache <code>s-maxage</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
        "apihelp-main-param-maxage": "Fixer l’entête HTTP de contrôle de cache <code>max-age</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
        "apihelp-main-param-assert": "Vérifier si l’utilisateur est connecté si la valeur est <kbd>user</kbd>, ou s’il a le droit d’un utilisateur robot si la valeur est <kbd>bot</kbd>.",
@@ -51,7 +51,7 @@
        "apihelp-main-param-responselanginfo": "Inclure les langues utilisées pour <var>uselang</var> et <var>errorlang</var> dans le résultat.",
        "apihelp-main-param-origin": "En accédant à l’API en utilisant une requête AJAX inter-domaines (CORS), mettre le domaine d’origine dans ce paramètre. Il doit être inclus dans toute requête de pre-flight, et doit donc faire partie de l’URI de la requête (pas du corps du POST).\n\nPour les requêtes authentifiées, il doit correspondre exactement à une des origines dans l’entête <code>Origin</code> header, donc il doit être fixé avec quelque chose comme <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Si ce paramètre ne correspond pas à l’entête <code>Origin</code>, une réponse 403 sera renvoyée. Si ce paramètre correspond à l’entête <code>Origin</code> et que l’origine est en liste blanche, des entêtes <code>Access-Control-Allow-Origin</code> et <code>Access-Control-Allow-Credentials</code> seront positionnés.\n\nPour les requêtes non authentifiées, spécifiez la valeur <kbd>*</kbd>. Cela positionnera l’entête <code>Access-Control-Allow-Origin</code>, mais <code>Access-Control-Allow-Credentials</code> vaudra <code>false</code> et toutes les données spécifiques à l’utilisateur seront filtrées.",
        "apihelp-main-param-uselang": "Langue à utiliser pour les traductions de message. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd> renvoie une liste de codes de langue, ou en spécifiant <kbd>user</kbd> pour utiliser la préférence de langue de l’utilisateur actuel, ou en spécifiant <kbd>content</kbd> pour utiliser le langage du contenu de ce wiki.",
-       "apihelp-main-param-errorformat": "Format à utiliser pour la sortie du texte d’avertissement et d’erreur.\n; plaintext: Wikitexte avec balises HTML supprimées et les entités remplacées.\n; wikitext: wikitexte non analysé.\n; html: HTML.\n; raw: Clé de message et paramètres.\n; none: Aucune sortie de texte, uniquement les codes erreur.\n; bc: Format utilisé avant MédiaWiki 1.29. <var>errorlang</var> et <var>errorsuselocal</var> sont ignorés.",
+       "apihelp-main-param-errorformat": "Format à utiliser pour la sortie du texte d’avertissement et d’erreur.\n; plaintext: Wikitexte avec balises HTML supprimées et les entités remplacées.\n; wikitext: wikitexte non analysé.\n; html: HTML.\n; raw: Clé de message et paramètres.\n; none: Aucune sortie de texte, uniquement les codes erreur.\n; bc: Format utilisé avant MediaWiki 1.29. <var>errorlang</var> et <var>errorsuselocal</var> sont ignorés.",
        "apihelp-main-param-errorlang": "Langue à utiliser pour les avertissements et les erreurs. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd> renvoyant une liste de codes de langue, ou spécifier <kbd>content</kbd> pour utiliser la langue du contenu de ce wiki, ou spécifier <kbd>uselang</kbd> pour utiliser la même valeur que le paramètre <var>uselang</var>.",
        "apihelp-main-param-errorsuselocal": "S’il est fourni, les textes d’erreur utiliseront des messages adaptés à la langue dans l’espace de noms {{ns:MediaWiki}}.",
        "apihelp-block-summary": "Bloquer un utilisateur.",
@@ -86,7 +86,7 @@
        "apihelp-clientlogin-example-login": "Commencer le processus de connexion au wiki en tant qu’utilisateur <kbd>Exemple</kbd> avec le mot de passe <kbd>ExempleMotDePasse</kbd>.",
        "apihelp-clientlogin-example-login2": "Continuer la connexion après une réponse de l’<samp>IHM</samp> pour l’authentification à deux facteurs, en fournissant un <var>OATHToken</var> valant <kbd>987654</kbd>.",
        "apihelp-compare-summary": "Obtenir la différence entre deux pages.",
-       "apihelp-compare-extended-description": "Vous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
+       "apihelp-compare-extended-description": "Vous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
        "apihelp-compare-param-fromtitle": "Premier titre à comparer.",
        "apihelp-compare-param-fromid": "ID de la première page à comparer.",
        "apihelp-compare-param-fromrev": "Première révision à comparer.",
        "apihelp-edit-param-summary": "Modifier le résumé. Également le titre de la section quand $1section=new et $1sectiontitle n’est pas défini.",
        "apihelp-edit-param-tags": "Modifier les balises à appliquer à la version.",
        "apihelp-edit-param-minor": "Marquer cette modification comme étant mineure.",
-       "apihelp-edit-param-notminor": "Ne pas marquer cette modification comme mineure, même si la préférence utilisateur « {{int:tog-minordefault}} » est positionnée.",
+       "apihelp-edit-param-notminor": "Ne pas marquer cette modification comme mineure, même si la préférence utilisateur « {{int:tog-minordefault}} » est positionnée.",
        "apihelp-edit-param-bot": "Marquer cette modification comme effectuée par un robot.",
        "apihelp-edit-param-basetimestamp": "Horodatage de la révision de base, utilisé pour détecter les conflits de modification. Peut être obtenu via [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
        "apihelp-edit-param-starttimestamp": "L'horodatage, lorsque le processus d'édition est démarré, est utilisé pour détecter les conflits de modification. Une valeur appropriée peut être obtenue en utilisant <var>[[Special:ApiHelp/main|curtimestamp]]</var> lors du démarrage du processus d'édition (par ex. en chargeant le contenu de la page à modifier).",
        "apihelp-opensearch-param-limit": "Nombre maximal de résultats à renvoyer.",
        "apihelp-opensearch-param-namespace": "Espaces de nom à rechercher.  Ignoré if <var>$1search</var> commence avec le préfixe d'un espace de noms valide.",
        "apihelp-opensearch-param-suggest": "Ne rien faire si <var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> vaut faux.",
-       "apihelp-opensearch-param-redirects": "Comment gérer les redirections :\n;return:Renvoie la redirection elle-même.\n;resolve:Renvoie la page cible. Peut renvoyer moins de $1limit résultats.\nPour des raisons historiques, la valeur par défaut est « return » pour $1format=json et « resolve » pour les autres formats.",
+       "apihelp-opensearch-param-redirects": "Comment gérer les redirections :\n;return:Renvoie la redirection elle-même.\n;resolve:Renvoie la page cible. Peut renvoyer moins de $1limit résultats.\nPour des raisons historiques, la valeur par défaut est « return » pour $1format=json et « resolve » pour les autres formats.",
        "apihelp-opensearch-param-format": "Le format de sortie.",
        "apihelp-opensearch-param-warningsaserror": "Si des avertissements apparaissent avec <kbd>format=json</kbd>, renvoyer une erreur d’API au lieu de les ignorer.",
        "apihelp-opensearch-example-te": "Trouver les pages commençant par <kbd>Te</kbd>.",
        "apihelp-parse-param-effectivelanglinks": "Inclut les liens de langue fournis par les extensions (à utiliser avec <kbd>$1prop=langlinks</kbd>).",
        "apihelp-parse-param-section": "Traiter uniquement le contenu de la section ayant ce numéro.\n\nQuand la valeur est <kbd>new</kbd>, traite <var>$1text</var> et <var>$1sectiontitle</var> comme s’ils correspondaient à une nouvelle section de la page.\n\nLa valeur <kbd>new</kbd> n’est autorisée que si <var>text</var> est défini.",
        "apihelp-parse-param-sectiontitle": "Nouveau titre de section quand <var>section</var> vaut <kbd>nouveau</kbd>.\n\nÀ la différence de la modification de page, cela ne revient pas à <var>summary</var> quand il est omis ou vide.",
-       "apihelp-parse-param-disablelimitreport": "Omettre le rapport de limite (« rapport de limite du nouveau PP ») de la sortie de l’analyseur.",
+       "apihelp-parse-param-disablelimitreport": "Omettre le rapport de limite (« rapport de limite du nouveau PP ») de la sortie de l’analyseur.",
        "apihelp-parse-param-disablepp": "Utiliser <var>$1disablelimitreport</var> à la place.",
        "apihelp-parse-param-disableeditsection": "Omettre les liens de modification de section de la sortie de l’analyseur.",
        "apihelp-parse-param-disabletidy": "Ne pas exécuter de nettoyage du code HTML (par exemple,  réagencer) sur la sortie de l'analyseur.",
        "apihelp-query+embeddedin-param-limit": "Combien de pages renvoyer au total.",
        "apihelp-query+embeddedin-example-simple": "Afficher les pages incluant <kbd>Template:Stub</kbd>.",
        "apihelp-query+embeddedin-example-generator": "Obtenir des informations sur les pages incluant <kbd>Template:Stub</kbd>.",
-       "apihelp-query+extlinks-summary": "Renvoyer toutes les URLs externes (non interwikis) des pages données.",
+       "apihelp-query+extlinks-summary": "Renvoyer toutes les URL externes (non interwikis) des pages données.",
        "apihelp-query+extlinks-param-limit": "Combien de liens renvoyer.",
        "apihelp-query+extlinks-param-protocol": "Protocole de l’URL. Si vide et <var>$1query</var> est positionné, le protocole est <kbd>http</kbd>. Laisser à la fois ceci et <var>$1query</var> vides pour lister tous les liens externes.",
        "apihelp-query+extlinks-param-query": "Rechercher une chaîne sans protocole. Utile pour vérifier si une certaine page contient une certaine URL externe.",
-       "apihelp-query+extlinks-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
+       "apihelp-query+extlinks-param-expandurl": "Étendre les URL relatives au protocole avec le protocole canonique.",
        "apihelp-query+extlinks-example-simple": "Obtenir une liste des liens externes de <kbd>Main Page</kbd>.",
        "apihelp-query+exturlusage-summary": "Énumérer les pages contenant une URL donnée.",
        "apihelp-query+exturlusage-param-prop": "Quelles informations inclure :",
        "apihelp-query+exturlusage-param-query": "Rechercher une chaîne sans protocole. Voyez [[Special:LinkSearch]]. Le laisser vide pour lister tous les liens externes.",
        "apihelp-query+exturlusage-param-namespace": "Les espaces de nom à énumérer.",
        "apihelp-query+exturlusage-param-limit": "Combien de pages renvoyer.",
-       "apihelp-query+exturlusage-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
+       "apihelp-query+exturlusage-param-expandurl": "Étendre les URL relatives au protocole avec le protocole canonique.",
        "apihelp-query+exturlusage-example-simple": "Afficher les pages avec un lien vers <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Énumérer séquentiellement tous les fichiers supprimés.",
        "apihelp-query+filearchive-param-from": "Le titre de l’image auquel démarrer l’énumération.",
        "apihelp-query+filearchive-param-limit": "Combien d’images renvoyer au total.",
        "apihelp-query+filearchive-param-dir": "La direction dans laquelle lister.",
        "apihelp-query+filearchive-param-sha1": "Hachage SHA1 de l’image. Écrase $1sha1base36.",
-       "apihelp-query+filearchive-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MédiaWiki).",
+       "apihelp-query+filearchive-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MediaWiki).",
        "apihelp-query+filearchive-param-prop": "Quelle information obtenir sur l’image :",
        "apihelp-query+filearchive-paramvalue-prop-sha1": "Ajoute le hachage SHA-1 pour l’image.",
        "apihelp-query+filearchive-paramvalue-prop-timestamp": "Ajoute l’horodatage à la version téléversée.",
        "apihelp-query+filerepoinfo-paramvalue-prop-local": "Si ce dépôt est local ou non.",
        "apihelp-query+filerepoinfo-paramvalue-prop-name": "La clé du dépôt — utilisée dans les valeurs de retour, par ex. <var>[[mw:Special:MyLanguage/Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var> et [[Special:ApiHelp/query+imageinfo|imageinfo]] return values.",
        "apihelp-query+filerepoinfo-paramvalue-prop-rootUrl": "Chemin de l’URL racine pour les chemins d’image.",
-       "apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "Chemin de l’URL racine pour l’installation de MédiaWiki du wiki du dépôt.",
+       "apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "Chemin de l’URL racine pour l’installation de MediaWiki du wiki du dépôt.",
        "apihelp-query+filerepoinfo-paramvalue-prop-server": "<var>[[mw:Special:MyLanguage/Manual:$wgServer|$wgServer]]</var> du wiki du dépôt, ou équivalent.",
        "apihelp-query+filerepoinfo-paramvalue-prop-thumbUrl": "Chemin de l’URL racine pour les chemins des vignettes.",
        "apihelp-query+filerepoinfo-paramvalue-prop-url": "Chemin de l’URL de la zone publique.",
        "apihelp-query+imageinfo-paramvalue-prop-extmetadata": "Liste les métadonnées mises en forme combinées depuis diverses sources. Les résultats sont au format HTML.",
        "apihelp-query+imageinfo-paramvalue-prop-archivename": "Ajoute le nom de fichier de la version d’archive pour les versions autres que la dernière.",
        "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "Ajoute la profondeur de bits de la version.",
-       "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Utilisé par la page Special:Upload pour obtenir de l’information sur un fichier existant. Non prévu pour être utilisé en dehors du cœur de MédiaWiki.",
+       "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Utilisé par la page Special:Upload pour obtenir de l’information sur un fichier existant. Non prévu pour être utilisé en dehors du cœur de MediaWiki.",
        "apihelp-query+imageinfo-paramvalue-prop-badfile": "Ajoute l'indication que le fichier est sur [[MediaWiki:Bad image list]]",
        "apihelp-query+imageinfo-param-limit": "Combien de révisions de fichier renvoyer par fichier.",
        "apihelp-query+imageinfo-param-start": "Horodatage auquel démarrer la liste.",
        "apihelp-query+info-example-simple": "Obtenir des informations sur la page <kbd>Main Page</kbd>.",
        "apihelp-query+info-example-protection": "Obtenir des informations générales et de protection sur la page <kbd>Main Page</kbd>.",
        "apihelp-query+iwbacklinks-summary": "Trouver toutes les pages qui ont un lien vers le lien interwiki indiqué.",
-       "apihelp-query+iwbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un préfixe, ou tous les liens vers un titre (avec un préfixe donné). Sans paramètre, équivaut à « tous les liens interwiki ».",
+       "apihelp-query+iwbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un préfixe, ou tous les liens vers un titre (avec un préfixe donné). Sans paramètre, équivaut à « tous les liens interwiki ».",
        "apihelp-query+iwbacklinks-param-prefix": "Préfixe pour l’interwiki.",
        "apihelp-query+iwbacklinks-param-title": "Lien interwiki à rechercher. Doit être utilisé avec <var>$1blprefix</var>.",
        "apihelp-query+iwbacklinks-param-limit": "Combien de pages renvoyer.",
        "apihelp-query+iwlinks-param-dir": "La direction dans laquelle lister.",
        "apihelp-query+iwlinks-example-simple": "Obtenir les liens interwiki de la page <kbd>Main Page</kbd>.",
        "apihelp-query+langbacklinks-summary": "Trouver toutes les pages qui ont un lien vers le lien de langue indiqué.",
-       "apihelp-query+langbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un code de langue, ou tous les liens vers un titre (avec une langue donnée). Sans paramètre équivaut à « tous les liens de langue ».\n\nNotez que cela peut ne pas prendre en compte les liens de langue ajoutés par les extensions.",
+       "apihelp-query+langbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un code de langue, ou tous les liens vers un titre (avec une langue donnée). Sans paramètre équivaut à « tous les liens de langue ».\n\nNotez que cela peut ne pas prendre en compte les liens de langue ajoutés par les extensions.",
        "apihelp-query+langbacklinks-param-lang": "Langue pour le lien de langue.",
        "apihelp-query+langbacklinks-param-title": "Lien interlangue à rechercher. Doit être utilisé avec $1lang.",
        "apihelp-query+langbacklinks-param-limit": "Combien de pages renvoyer au total.",
        "apihelp-query+languageinfo-summary": "Renvoyer des informations sur les langues disponibles.",
        "apihelp-query+languageinfo-extended-description": "[[mw:API:Query#Continuing queries|Un prolongement]] peut être appliqué si la récupération de l’information prend trop de temps pour une requête.",
        "apihelp-query+languageinfo-param-prop": "Quelle information obtenir pour chaque langue.",
-       "apihelp-query+languageinfo-paramvalue-prop-code": "Le code de langue (ce code est spécifique à MédiaWiki, bien qu’il y ait des recouvrements avec d’autres standards).",
+       "apihelp-query+languageinfo-paramvalue-prop-code": "Le code de langue (ce code est spécifique à MediaWiki, bien qu’il y ait des recouvrements avec d’autres standards).",
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "Le code de langue BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "La direction d’écriture de la langue (<code>ltr</code> ou <code>rtl</code>).",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme d’une langue, c’est-à-dire son nom dans cette langue.",
        "apihelp-query+siteinfo-paramvalue-prop-fileextensions": "Renvoie la liste des extensions de fichiers (types de fichiers) autorisées au téléversement.",
        "apihelp-query+siteinfo-paramvalue-prop-rightsinfo": "Renvoie l’information sur les droits du wiki (sa licence), si elle est disponible.",
        "apihelp-query+siteinfo-paramvalue-prop-restrictions": "Renvoie l’information sur les types de restriction disponibles (protection).",
-       "apihelp-query+siteinfo-paramvalue-prop-languages": "Renvoie une liste des langues que MédiaWiki prend en charge (éventuellement localisée en utilisant <var>$1inlanguagecode</var>).",
+       "apihelp-query+siteinfo-paramvalue-prop-languages": "Renvoie une liste des langues que MediaWiki prend en charge (éventuellement localisée en utilisant <var>$1inlanguagecode</var>).",
        "apihelp-query+siteinfo-paramvalue-prop-languagevariants": "Renvoie une liste de codes de langue pour lesquels [[mw:Special:MyLanguage/LanguageConverter|LanguageConverter]] est activé, et les variantes prises en charge pour chacun.",
        "apihelp-query+siteinfo-paramvalue-prop-skins": "Renvoie une liste de tous les habillages activés (éventuellement localisé en utilisant <var>$1inlanguagecode</var>, sinon dans la langue du contenu).",
        "apihelp-query+siteinfo-paramvalue-prop-extensiontags": "Renvoie une liste des balises d’extension de l’analyseur.",
        "apihelp-query+users-paramvalue-prop-editcount": "Ajoute le compteur de modifications de l’utilisateur.",
        "apihelp-query+users-paramvalue-prop-registration": "Ajoute l’horodatage d’inscription de l’utilisateur.",
        "apihelp-query+users-paramvalue-prop-emailable": "Marque si l’utilisateur peut et veut recevoir des courriels via [[Special:Emailuser]].",
-       "apihelp-query+users-paramvalue-prop-gender": "Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
+       "apihelp-query+users-paramvalue-prop-gender": "Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
        "apihelp-query+users-paramvalue-prop-centralids": "Ajoute les IDs centraux et l’état d’attachement de l’utilisateur.",
        "apihelp-query+users-paramvalue-prop-cancreate": "Indique si un compte peut être créé pour les noms d’utilisateurs valides mais non enregistrés.",
        "apihelp-query+users-param-attachedwiki": "Avec <kbd>$1prop=centralids</kbd>, indiquer si l’utilisateur est attaché au wiki identifié par cet ID.",
        "apihelp-rsd-summary": "Exporter un schéma RSD (Découverte Très Simple).",
        "apihelp-rsd-example-simple": "Exporter le schéma RSD",
        "apihelp-setnotificationtimestamp-summary": "Mettre à jour l’horodatage de notification pour les pages suivies.",
-       "apihelp-setnotificationtimestamp-extended-description": "Cela affecte la mise en évidence des pages modifiées dans la liste de suivi et l’historique, et l’envoi de courriel quand la préférence « {{int:tog-enotifwatchlistpages}} » est activée.",
+       "apihelp-setnotificationtimestamp-extended-description": "Cela affecte la mise en évidence des pages modifiées dans la liste de suivi et l’historique, et l’envoi de courriel quand la préférence « {{int:tog-enotifwatchlistpages}} » est activée.",
        "apihelp-setnotificationtimestamp-param-entirewatchlist": "Travailler sur toutes les pages suivies.",
        "apihelp-setnotificationtimestamp-param-timestamp": "Horodatage auquel dater la notification.",
        "apihelp-setnotificationtimestamp-param-torevid": "Révision pour laquelle fixer l’horodatage de notification (une page uniquement).",
        "apihelp-unlinkaccount-summary": "Supprimer un compte tiers lié de l’utilisateur actuel.",
        "apihelp-unlinkaccount-example-simple": "Essayer de supprimer le lien de l’utilisateur actuel pour le fournisseur associé avec <kbd>FooAuthenticationRequest</kbd>.",
        "apihelp-upload-summary": "Téléverser un fichier, ou obtenir l’état des téléversements en cours.",
-       "apihelp-upload-extended-description": "Plusieurs méthodes sont disponibles :\n* Téléverser directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Téléverser le fichier par morceaux, en utilisant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.\n* Pour que le serveur MédiaWiki cherche un fichier depuis une URL, utilisez le paramètre <var>$1url</var>.\n* Terminer un téléversement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléversement de fichier (par exemple en utilisant <code>multipart/form-data</code>) en envoyant le <var>$1file</var>.",
+       "apihelp-upload-extended-description": "Plusieurs méthodes sont disponibles :\n* Téléverser directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Téléverser le fichier par morceaux, en utilisant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.\n* Pour que le serveur MediaWiki cherche un fichier depuis une URL, utilisez le paramètre <var>$1url</var>.\n* Terminer un téléversement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléversement de fichier (par exemple en utilisant <code>multipart/form-data</code>) en envoyant le <var>$1file</var>.",
        "apihelp-upload-param-filename": "Nom de fichier cible.",
        "apihelp-upload-param-comment": "Téléverser le commentaire. Utilisé aussi comme texte de la page initiale pour les nouveaux fichiers si <var>$1text</var> n’est pas spécifié.",
        "apihelp-upload-param-tags": "Modifier les balises à appliquer à l’entrée du journal de téléversement et à la révision de la page du fichier.",
        "api-pageset-param-titles": "Une liste des titres sur lesquels travailler.",
        "api-pageset-param-pageids": "Une liste des IDs de pages sur lesquelles travailler.",
        "api-pageset-param-revids": "Une liste des IDs de révisions sur lesquelles travailler.",
-       "api-pageset-param-generator": "Obtenir la liste des pages sur lesquelles travailler en exécutant le module de requête spécifié.\n\n<strong>NOTE :<strong> les noms de paramètre du générateur doivent être préfixés avec un « g », voir les exemples.",
+       "api-pageset-param-generator": "Obtenir la liste des pages sur lesquelles travailler en exécutant le module de requête spécifié.\n\n<strong>NOTE :<strong> les noms de paramètre du générateur doivent être préfixés avec un « g », voir les exemples.",
        "api-pageset-param-redirects-generator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>, et dans les pages renvoyées par <var>$1generator</var>.",
        "api-pageset-param-redirects-nogenerator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>.",
        "api-pageset-param-converttitles": "Convertir les titres dans d’autres variantes si nécessaire. Fonctionne uniquement si la langue de contenu du wiki prend en charge la conversion en variantes. Les langues qui prennent en charge la conversion en variantes incluent $1.",
        "api-help-param-templated-var-first": "<var>&#x7B;$1&#x7D;</var> dans le nom du paramètre doit être remplacé par des valeurs de <var>$2</var>",
        "api-help-param-templated-var": "<var>&#x7B;$1&#x7D;</var> par les valeurs de <var>$2</var>",
        "api-help-datatypes-header": "Type de données",
-       "api-help-datatypes": "Les entrées dans MédiaWiki doivent être en UTF-8 à la norme NFC. MédiaWiki peut tenter de convertir d’autres types d’entrées, mais cela peut faire échouer certaines opérations (comme les [[Special:ApiHelp/edit|modifications]] avec contrôles MD5).\n\nCertains types de paramètres dans les requêtes de l’API nécessitent plus d’explication&nbsp;:\n;boolean\n:Les paramètres booléens fonctionnent comme des cases à cocher HTML&nbsp;: si le paramètre est spécifié, quelle que soit sa valeur, il est considéré comme vrai. Pour une valeur fausse, enlever complètement le paramètre.\n;timestamp\n:Les horodatages peuvent être spécifiés sous différentes formes, voir [[mw:Special:MyLanguage/Timestamp|les formats d’entrées de la librairie Timestampdocumentés sur mediawiki.org]] pour plus de détails. La date et heure ISO 8601 est recommandée&nbsp;: <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd>. De plus, la chaîne de caractères <kbd>now</kbd> peut être utilisée pour spécifier le fuseau horaire actuel.\n;séparateur multi-valeurs alternatif\n:Les paramètres prenant plusieurs valeurs sont normalement validés lorsque celles-ci sont séparées par le caractère «&nbsp;pipe&nbsp;» (|), ex. <kbd>paramètre=valeur1|valeur2</kbd> ou <kbd>paramètre=valeur1%7Cvaleur2</kbd>. Si une valeur doit contenir le caractère «&nbsp;pipe&nbsp;», utiliser U+001F (séparateur de sous-articles) comme séparateur ''et'' la préfixer de U+001F, ex. <kbd>paramètre=%1Fvaleur1%1Fvaleur2</kbd>.",
+       "api-help-datatypes": "Les entrées dans MediaWiki doivent être en UTF-8 à la norme NFC. MediaWiki peut tenter de convertir d’autres types d’entrées, mais cela peut faire échouer certaines opérations (comme les [[Special:ApiHelp/edit|modifications]] avec contrôles MD5).\n\nCertains types de paramètres dans les requêtes de l’API nécessitent plus d’explication&nbsp;:\n;boolean\n:Les paramètres booléens fonctionnent comme des cases à cocher HTML&nbsp;: si le paramètre est spécifié, quelle que soit sa valeur, il est considéré comme vrai. Pour une valeur fausse, enlever complètement le paramètre.\n;timestamp\n:Les horodatages peuvent être spécifiés sous différentes formes, voir [[mw:Special:MyLanguage/Timestamp|les formats d’entrées de la librairie Timestampdocumentés sur mediawiki.org]] pour plus de détails. La date et heure ISO 8601 est recommandée&nbsp;: <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd>. De plus, la chaîne de caractères <kbd>now</kbd> peut être utilisée pour spécifier le fuseau horaire actuel.\n;séparateur multi-valeurs alternatif\n:Les paramètres prenant plusieurs valeurs sont normalement validés lorsque celles-ci sont séparées par le caractère «&nbsp;pipe&nbsp;» (|), ex. <kbd>paramètre=valeur1|valeur2</kbd> ou <kbd>paramètre=valeur1%7Cvaleur2</kbd>. Si une valeur doit contenir le caractère «&nbsp;pipe&nbsp;», utiliser U+001F (séparateur de sous-articles) comme séparateur ''et'' la préfixer de U+001F, ex. <kbd>paramètre=%1Fvaleur1%1Fvaleur2</kbd>.",
        "api-help-templatedparams-header": "Paramètres de modèle",
        "api-help-templatedparams": "Les paramètres de modèle supportent les cas où un module d’API a besoin d’une valeur pour chaque valeur d’un autre paramètre quelconque. Par exemple, s’il y avait un module d’API pour demander un fruit, il pourrait avoir un paramètre <var>fruits</var> pour spécifier quels fruits sont demandés et un paramètre de modèle <var>{fruit}-quantité</var> pour spécifier la quantité demandée de chaque fruit. Un client de l’API qui voudrait une pomme, cinq bananes et vingt fraises pourrait alors faire une requête comme <kbd>fruits=pommes|bananes|fraises&pommes-quantité=1&bananes-quantité=5&fraises-quantité=20</kbd>.",
        "api-help-param-type-limit": "Type : entier ou <kbd>max</kbd>",
        "api-help-param-multi-all": "Pour spécifier toutes les valeurs, utiliser <kbd>$1</kbd>.",
        "api-help-param-default": "Par défaut : $1",
        "api-help-param-default-empty": "Par défaut : <span class=\"apihelp-empty\">(vide)</span>",
-       "api-help-param-token": "Un jeton « $1 » récupéré par [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+       "api-help-param-token": "Un jeton « $1 » récupéré par [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
        "api-help-param-token-webui": "Pour rester compatible, le jeton utilisé dans l’IHM web est aussi accepté.",
        "api-help-param-disabled-in-miser-mode": "Désactivé à cause du [[mw:Special:MyLanguage/Manual:$wgMiserMode|mode minimal]].",
        "api-help-param-limited-in-miser-mode": "<strong>NOTE :</strong> Du fait du [[mw:Special:MyLanguage/Manual:$wgMiserMode|mode minimal]], utiliser cela peut aboutir à moins de résultats que <var>$1limit</var> renvoyés avant de continuer ; dans les cas extrêmes, zéro résultat peut être renvoyé.",
        "apierror-appendnotsupported": "Impossible d’ajouter aux pages utilisant le modèle de contenu $1.",
        "apierror-articleexists": "L’article que vous essayez de créer l’a déjà été.",
        "apierror-assertbotfailed": "La vérification que l’utilisateur a le droit <code>bot</code> a échoué.",
-       "apierror-assertnameduserfailed": "La vérification que l’utilisateur est « $1 » a échoué.",
+       "apierror-assertnameduserfailed": "La vérification que l’utilisateur est « $1 » a échoué.",
        "apierror-assertuserfailed": "La vérification que l’utilisateur est connecté a échoué.",
        "apierror-autoblocked": "Votre adresse IP a été bloquée automatiquement, parce qu’elle a été utilisée par un utilisateur bloqué.",
        "apierror-bad-badfilecontexttitle": "Titre invalide dans le paramètre <var>$1badfilecontexttitle</var> .",
        "apierror-badgenerator-unknown": "<kbd>generator=$1</kbd> inconnu.",
        "apierror-badip": "Paramètre IP non valide.",
        "apierror-badmd5": "Le hachage MD5 fourni n’était pas correct.",
-       "apierror-badmodule-badsubmodule": "Le module <kbd>$1</kbd> n’a pas de sous-module « $2 ».",
+       "apierror-badmodule-badsubmodule": "Le module <kbd>$1</kbd> n’a pas de sous-module « $2 ».",
        "apierror-badmodule-nosubmodules": "Le module <kbd>$1</kbd> n’a pas de sous-modules.",
        "apierror-badparameter": "Valeur non valide pour le paramètre <var>$1</var>.",
        "apierror-badquery": "Requête invalide.",
-       "apierror-badtimestamp": "Valeur non valide « $2 » pour le paramètre de référence horaire  <var>$1</var>.",
+       "apierror-badtimestamp": "Valeur non valide « $2 » pour le paramètre de référence horaire  <var>$1</var>.",
        "apierror-badtoken": "Jeton CSRF non valide.",
        "apierror-badupload": "Le paramètre de téléversement de fichier <var>$1</var> n’est pas un téléversement de fichier ; assurez-vous d’utiliser <code>multipart/form-data</code> pour votre POST et d’inclure un nom de fichier dans l’entête <code>Content-Disposition</code>.",
-       "apierror-badurl": "Valeur « $2 » non valide pour le paramètre d’URL <var>$1</var>.",
-       "apierror-baduser": "Valeur « $2 » non valide pour le paramètre utilisateur <var>$1</var>.",
+       "apierror-badurl": "Valeur « $2 » non valide pour le paramètre d’URL <var>$1</var>.",
+       "apierror-baduser": "Valeur « $2 » non valide pour le paramètre utilisateur <var>$1</var>.",
        "apierror-badvalue-notmultivalue": "La séparation multi-valeur U+001F ne peut être utilisée que pour des paramètres multi-valeurs.",
        "apierror-bad-watchlist-token": "Jeton de liste de suivi fourni non valide. Veuillez mettre un jeton valide dans [[Special:Preferences]].",
        "apierror-blockedfrommail": "Vous avez été bloqué pour l’envoi de courriel.",
        "apierror-cantoverwrite-sharedfile": "Le fichier cible existe dans un dépôt partagé et vous n’avez pas le droit de l’écraser.",
        "apierror-cantsend": "Vous n’êtes pas connecté, vous n’avez pas d’adresse de courriel confirmée, ou vous n’êtes pas autorisé à envoyer des courriels aux autres utilisateurs, donc vous ne pouvez envoyer de courriel.",
        "apierror-cantundelete": "Impossible d’annuler : les révisions demandées peuvent ne plus exister, ou avoir déjà été annulées.",
+       "apierror-cantview-deleted-description": "Vous n’avez pas le droit d’afficher les descriptions des fichiers supprimés.",
+       "apierror-cantview-deleted-metadata": "Vous n’avez pas le droit d’afficher les métadonnées des fichiers supprimés.",
        "apierror-changeauth-norequest": "Échec à la création de la requête de modification.",
        "apierror-chunk-too-small": "La taille minimale d’un segment est de $1 {{PLURAL:$1|octet|octets}} pour les segments hors le dernier.",
        "apierror-cidrtoobroad": "Les plages CIDR $1 plus large que /$2 ne sont pas acceptées.",
        "apierror-invalidsection": "Le paramètre <var>section</var> doit être un ID de section valide ou <kbd>new</kbd>.",
        "apierror-invalidsha1base36hash": "Le hachage SHA1Base36 fourni n’est pas valide.",
        "apierror-invalidsha1hash": "Le hachage SHA1 fourni n’est pas valide.",
-       "apierror-invalidtitle": "Mauvais titre « $1 ».",
+       "apierror-invalidtitle": "Mauvais titre « $1 ».",
        "apierror-invalidurlparam": "Valeur non valide pour <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
        "apierror-invaliduser": "Nom d'utilisateur invalide \"$1\".",
        "apierror-invaliduserid": "L'ID d'utilisateur <var>$1</var> n'est pas valide.",
        "apierror-paramempty": "Le paramètre <var>$1</var> ne peut pas être vide.",
        "apierror-parsetree-notwikitext": "<kbd>prop=parsetree</kbd> n’est pris en charge que pour le contenu wikitexte.",
        "apierror-parsetree-notwikitext-title": "<kbd>prop=parsetree</kbd> n’est pris en charge que pour le contenu wikitexte. $1 utilise le modèle de contenu $2.",
-       "apierror-pastexpiry": "La date d’expiration « $1 » est dépassée.",
+       "apierror-pastexpiry": "La date d’expiration « $1 » est dépassée.",
        "apierror-permissiondenied": "Vous n’avez pas le droit de $1.",
        "apierror-permissiondenied-generic": "Autorisation refusée.",
        "apierror-permissiondenied-patrolflag": "Vous avez besoin du droit <code>patrol</code> ou <code>patrolmarks</code> pour demander le drapeau patrouillé.",
        "apierror-permissiondenied-unblock": "Vous n’avez pas le droit de débloquer les utilisateurs.",
        "apierror-prefixsearchdisabled": "La recherche de préfixe est désactivée en mode misérable.",
        "apierror-promised-nonwrite-api": "L’entête HTTP <code>Promise-Non-Write-API-Action</code> ne peut pas être envoyé aux modules de l’API en mode écriture.",
-       "apierror-protect-invalidaction": "Type de protection non valide « $1 ».",
-       "apierror-protect-invalidlevel": "Niveau de protection non valide « $1 ».",
+       "apierror-protect-invalidaction": "Type de protection non valide « $1 ».",
+       "apierror-protect-invalidlevel": "Niveau de protection non valide « $1 ».",
        "apierror-ratelimited": "Vous avez dépassé votre limite de débit. Veuillez attendre un peu et réessayer.",
        "apierror-readapidenied": "Vous avez besoin du droit de lecture pour utiliser ce module.",
        "apierror-readonly": "Ce wiki est actuellement en mode lecture seule.",
        "apiwarn-deprecation-login-token": "La récupération d’un jeton via <kbd>action=login</kbd> est désuète. Utilisez <kbd>action=query&meta=tokens&type=login</kbd> à la place.",
        "apiwarn-deprecation-missingparam": "Comme <var>$1</var> n’a pas été spécifié, un format ancien a été utilisé pour la sortie. Ce format est obsolète, et dans le futur, le nouveau format sera toujours utilisé.",
        "apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est désuet.",
-       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MediaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
        "apiwarn-deprecation-post-without-content-type": "Une requête POST a été faite sans entête <code>Content-Type</code>. Cela ne fonctionne pas de façon fiable.",
        "apiwarn-deprecation-purge-get": "L’utilisation de <kbd>action=purge</kbd> via un GET est désuète. Utiliser POST à la place.",
        "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
        "apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
        "apiwarn-errorprinterfailed": "Erreur échec imprimante. Nouvel essai sans paramètres.",
        "apiwarn-ignoring-invalid-templated-value": "Ignorer la valeur <kbd>$2</kbd> dans <var>$1</var> en traitant les paramètres de modèle.",
-       "apiwarn-invalidcategory": "« $1 » n'est pas une catégorie.",
-       "apiwarn-invalidtitle": "« $1 » n’est pas un titre valide.",
+       "apiwarn-invalidcategory": "« $1 » n'est pas une catégorie.",
+       "apiwarn-invalidtitle": "« $1 » n’est pas un titre valide.",
        "apiwarn-invalidxmlstylesheetext": "Une feuille de style doit avoir une extension <code>.xsl</code>.",
        "apiwarn-invalidxmlstylesheet": "Feuille de style spécifiée non valide ou inexistante.",
        "apiwarn-invalidxmlstylesheetns": "La feuille de style devrait être dans l’espace de noms {{ns:MediaWiki}}.",
        "apiwarn-moduleswithoutvars": "La propriété <kbd>modules</kbd> a été définie mais pas <kbd>jsconfigvars</kbd> ni <kbd>encodedjsconfigvars</kbd>. Les variables de configuration sont nécessaires pour une utilisation correcte du module.",
-       "apiwarn-notfile": "« $1 » n'est pas un fichier.",
+       "apiwarn-notfile": "« $1 » n'est pas un fichier.",
        "apiwarn-nothumb-noimagehandler": "Impossible de créer la vignette car $1 n’a pas de gestionnaire d’image associé.",
        "apiwarn-parse-nocontentmodel": "Ni <var>title</var> ni <var>contentmodel</var> n’ont été fournis, $1 est supposé.",
        "apiwarn-parse-revidwithouttext": "<var>revid</var> utilisé sans <var>text</var>, et les propriétés de la page analysée ont été demandées. Vouliez-vous utiliser <var>oldid</var> au lieu de <var>revid</var> ?",
        "apiwarn-parse-titlewithouttext": "<var>title</var> utilisé sans <var>text</var>, et les propriétés de page analysées sont nécessaires. Voulez-vous dire que vous voulez utiliser <var>page</var> à la place de <var>title</var> ?",
        "apiwarn-redirectsandrevids": "La résolution de la redirection ne peut pas être utilisée avec le paramètre <var>revids</var>. Toutes les redirections vers lesquelles pointent <var>revids</var> n’ont pas été résolues.",
-       "apiwarn-tokennotallowed": "L'action « $1 » n'est pas autorisée pour l'utilisateur actuel.",
+       "apiwarn-tokennotallowed": "L'action « $1 » n'est pas autorisée pour l'utilisateur actuel.",
        "apiwarn-tokens-origin": "Les jetons ne peuvent pas être obtenus quand la politique de même origine n’est pas appliquée.",
        "apiwarn-truncatedresult": "Ce résultat a été tronqué parce que sinon, il dépasserait la limite de $1 octets.",
-       "apiwarn-unclearnowtimestamp": "Passer « $2 » comme paramètre d’horodatage <var>$1</var> est obsolète. Si, pour une raison quelconque, vous avez besoin de spécifier explicitement l’heure courante sans la recalculer du côté client, utilisez <kbd>now</kbd>.",
+       "apiwarn-unclearnowtimestamp": "Passer « $2 » comme paramètre d’horodatage <var>$1</var> est obsolète. Si, pour une raison quelconque, vous avez besoin de spécifier explicitement l’heure courante sans la recalculer du côté client, utilisez <kbd>now</kbd>.",
        "apiwarn-unrecognizedvalues": "{{PLURAL:$3|Valeur non reconnue|Valeurs non reconnues}} pour le paramètre <var>$1</var> : $2.",
        "apiwarn-unsupportedarray": "Le paramètre <var>$1</var> utilise une syntaxe PHP de tableau non prise en charge.",
        "apiwarn-urlparamwidth": "Valeur de la largeur définie dans <var>$1urlparam</var> ($2) ignorée en faveur de la largeur calculée à partir de <var>$1urlwidth</var>/<var>$1urlheight</var> ($3).",
index e71a9dc..d8ff539 100644 (file)
@@ -17,7 +17,8 @@
                        "InternerowyGołąb",
                        "CiaPan",
                        "Vlad5250",
-                       "Railfail536"
+                       "Railfail536",
+                       "Rail"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentacja]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista dyskusyjna]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Ogłoszenia dotyczące API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Błędy i propozycje]\n</div>\n<strong>Stan:</strong> Wszystkie funkcje opisane na tej stronie powinny działać, ale API nadal jest aktywnie rozwijane i mogą się zmienić w dowolnym czasie. Subskrybuj [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ listę dyskusyjną mediawiki-api-announce], aby móc na bieżąco dowiadywać się o aktualizacjach.\n\n<strong>Błędne żądania:</strong> Gdy zostanie wysłane błędne żądanie do API, zostanie wysłany w odpowiedzi nagłówek HTTP z kluczem \"MediaWiki-API-Error\" i zarówno jego wartość jak i wartość kodu błędu wysłanego w odpowiedzi będą miały taką samą wartość. Aby uzyskać więcej informacji, zobacz [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Błędy i ostrzeżenia]].\n\n<strong>Testowanie:</strong> Aby łatwo testować żądania API, zobacz [[Special:ApiSandbox]].",
        "apierror-cantimport": "Nie masz uprawnień do importowania stron.",
        "apierror-cantsend": "Nie jesteś zalogowany, nie masz potwierdzonego adresu e-mail, albo nie masz prawa wysyłać e-maili do innych użytkowników, więc nie możesz wysłać wiadomości e-mail.",
        "apierror-cantundelete": "Nie można przywrócić: dana wersja nie istnieje albo została już przywrócona.",
+       "apierror-cantview-deleted-description": "Nie masz uprawnień do podglądu opisów usuniętych plików.",
+       "apierror-cantview-deleted-metadata": "Nie masz uprawnień do podglądu metadanych usuniętych plików.",
        "apierror-exceptioncaught": "[$1] Stwierdzono wyjątek: $2",
        "apierror-filedoesnotexist": "Plik nie istnieje.",
        "apierror-import-unknownerror": "Nieznany błąd podczas importowania: $1.",
index 87f056b..b648443 100644 (file)
        "apierror-cantoverwrite-sharedfile": "{{doc-apierror}}",
        "apierror-cantsend": "{{doc-apierror}}",
        "apierror-cantundelete": "{{doc-apierror}}",
+       "apierror-cantview-deleted-comment": "{{doc-apierror}}",
+       "apierror-cantview-deleted-description": "{{doc-apierror}}",
+       "apierror-cantview-deleted-metadata": "{{doc-apierror}}",
+       "apierror-cantview-deleted-revision-content": "{{doc-apierror}}",
        "apierror-changeauth-norequest": "{{doc-apierror}}",
        "apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.",
        "apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.",
index c91976f..4d02204 100644 (file)
@@ -37,7 +37,8 @@
                        "Edward Chernenko",
                        "Vlad5250",
                        "Diralik",
-                       "DmitTrix"
+                       "DmitTrix",
+                       "Марио"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Документация]]\n* [[mw:Special:MyLanguage/API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n</div>\n<strong>Статус:</strong> MediaWiki API — зрелый и стабильный интерфейс, активно поддерживаемый и улучшаемый. Мы стараемся избегать ломающих изменений, однако изредка они могут быть необходимы. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом «MediaWiki-API-Error», после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Ошибки и предупреждения]].\n\n<p class=\"mw-apisandbox-link\"><strong>Тестирование:</strong> для удобства тестирования API-запросов, см. [[Special:ApiSandbox]].</p>",
@@ -94,6 +95,7 @@
        "apihelp-compare-param-fromid": "Идентификатор первой сравниваемой страницы.",
        "apihelp-compare-param-fromrev": "Первая сравниваемая версия.",
        "apihelp-compare-param-frompst": "Выполнить преобразование перед записью правки (PST) над <var>fromtext-&#x7B;slot}</var>.",
+       "apihelp-compare-param-fromslots": "Переопределение содержимого версии, заданной параметром <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>.\n\nЭтот параметр определяет слоты, которые должны быть изменены. Используйте <var>fromtext-&#x7B;slot}</var>, <var>fromcontentmodel-&#x7B;slot}</var>, и <var>fromcontentformat-&#x7B;slot}</var> для определения содержимого для каждого слота.",
        "apihelp-compare-param-fromtext": "Укажите <kbd>fromslots=main</kbd> и используйте <var>fromtext-main</var>.",
        "apihelp-compare-param-fromcontentmodel": "Укажите <kbd>fromslots=main</kbd> и используйте <var>fromcontentmodel-main</var>.",
        "apihelp-compare-param-fromcontentformat": "Укажите <kbd>fromslots=main</kbd> и используйте <var>fromcontentformat-main</var>.",
        "apihelp-query+blocks-paramvalue-prop-reason": "Добавляет причину блокировки.",
        "apihelp-query+blocks-paramvalue-prop-range": "Добавляет диапазон IP-адресов, затронутых блокировкой.",
        "apihelp-query+blocks-paramvalue-prop-flags": "Добавляет бану метку (autoblock, anonoly, и так далее).",
+       "apihelp-query+blocks-paramvalue-prop-restrictions": "Добавляет ограничения частичных блокировок, если блокировка не действует во всём проекте.",
        "apihelp-query+blocks-param-show": "Показать только элементы, удовлетворяющие этим критериям.\nНапример, чтобы отобразить только бессрочные блокировки IP-адресов, установите <kbd>$1show=ip|!temp</kbd>.",
        "apihelp-query+blocks-example-simple": "Список блокировок.",
        "apihelp-query+blocks-example-users": "Список блокировок участников <kbd>Alice</kbd> и <kbd>Bob</kbd>.",
        "apierror-bad-watchlist-token": "Предоставлен некорректный токен списка наблюдения. Пожалуйста, установите корректный токен в [[Special:Preferences]].",
        "apierror-blockedfrommail": "Отправка электронной почты была для вас заблокирована.",
        "apierror-blocked": "Редактирование было для вас заблокировано.",
+       "apierror-blocked-partial": "Вы были заблокированы от редактирования этой страницы.",
        "apierror-botsnotsupported": "Этот интерфейс не поддерживается для ботов.",
        "apierror-cannot-async-upload-file": "Параметры <var>async</var> и <var>file</var> не могут применяться вместе. Если вы хотите ассинхронно обработать загруженный файл, сначала загрузите его во временное хранилище (используя параметр <var>stash</var>), а затем опубликуйте этот файл ассинхронно (используя параметры <var>filekey</var> и <var>async</var>).",
        "apierror-cannotreauthenticate": "Это действие недоступно, так как ваша личность не может быть подтверждена.",
index 14a7717..7ee3751 100644 (file)
        "apierror-cantoverwrite-sharedfile": "目標檔案存在於分享儲存庫上,因此您沒有權限來覆蓋掉。",
        "apierror-cantsend": "您尚未登入,您沒有已確認的電子郵件地址,或是您未被允許發送電子郵件給其他人,因此您不能發送電子郵件。",
        "apierror-cantundelete": "無法取消刪除:請求的修訂可能不存在,或是可能已被取消刪除。",
+       "apierror-cantview-deleted-description": "您沒有權限來檢視被刪除檔案的描述內容。",
+       "apierror-cantview-deleted-metadata": "您沒有權限來檢視被刪除檔案的詮釋資料。",
        "apierror-changeauth-norequest": "建立更改請求失敗。",
        "apierror-chunk-too-small": "對於非最終塊,最小塊的大小為 $1 {{PLURAL:$1|位元組|位元組}}。",
        "apierror-cidrtoobroad": "不能接受超出 /$2 的 $1 CIDR 範圍。",
index e7527d1..bc70d5e 100644 (file)
@@ -247,12 +247,18 @@ abstract class AuthenticationRequest {
 
        /**
         * Select a request by class name.
+        *
+        * @codingStandardsIgnoreStart
+        * @phan-template T
+        * @codingStandardsIgnoreEnd
         * @param AuthenticationRequest[] $reqs
         * @param string $class Class name
+        * @phan-param class-string<T> $class
         * @param bool $allowSubclasses If true, also returns any request that's a subclass of the given
         *   class.
         * @return AuthenticationRequest|null Returns null if there is not exactly
         *  one matching request.
+        * @phan-return T|null
         */
        public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
                $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
index c831fc8..25a1754 100644 (file)
@@ -96,7 +96,9 @@ class ResetPasswordSecondaryAuthenticationProvider extends AbstractSecondaryAuth
                        }
                }
 
+               /** @var PasswordAuthenticationRequest $needReq */
                $needReq = $data->req ?? new PasswordAuthenticationRequest();
+               '@phan-var PasswordAuthenticationRequest $needReq';
                if ( !$needReq->action ) {
                        $needReq->action = AuthManager::ACTION_CHANGE;
                }
index 6007abd..b1a8e21 100644 (file)
@@ -24,7 +24,6 @@ namespace MediaWiki\Block;
 
 use ActorMigration;
 use AutoCommitUpdate;
-use BadMethodCallException;
 use CommentStore;
 use DeferredUpdates;
 use Hooks;
@@ -161,47 +160,6 @@ class DatabaseBlock extends AbstractBlock {
                }
        }
 
-       /**
-        * Return the list of ipblocks fields that should be selected to create
-        * a new block.
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return array
-        */
-       public static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->ipb_by or $row->ipb_by_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               wfDeprecated( __METHOD__, '1.31' );
-               return [
-                       'ipb_id',
-                       'ipb_address',
-                       'ipb_by',
-                       'ipb_by_text',
-                       'ipb_by_actor' => 'NULL',
-                       'ipb_timestamp',
-                       'ipb_auto',
-                       'ipb_anon_only',
-                       'ipb_create_account',
-                       'ipb_enable_autoblock',
-                       'ipb_expiry',
-                       'ipb_deleted',
-                       'ipb_block_email',
-                       'ipb_allow_usertalk',
-                       'ipb_parent_block_id',
-                       'ipb_sitewide',
-               ] + CommentStore::getStore()->getFields( 'ipb_reason' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new block object.
index 8018117..b2f2452 100644 (file)
@@ -89,6 +89,7 @@ class LinkCache {
         *
         * @param bool|null $update
         * @return bool
+        * @deprecated Since 1.34
         */
        public function forUpdate( $update = null ) {
                return wfSetVar( $this->mForUpdate, $update );
index 8f816d9..bc0bbfa 100644 (file)
@@ -78,8 +78,6 @@ class UserCache {
         * @param string $caller The calling method
         */
        public function doQuery( array $userIds, $options = [], $caller = '' ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $usersToCheck = [];
                $usersToQuery = [];
 
@@ -100,21 +98,12 @@ class UserCache {
                // Lookup basic info for users not yet loaded...
                if ( count( $usersToQuery ) ) {
                        $dbr = wfGetDB( DB_REPLICA );
-                       $tables = [ 'user' ];
+                       $tables = [ 'user', 'actor' ];
                        $conds = [ 'user_id' => $usersToQuery ];
-                       $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id' ];
-                       $joinConds = [];
-
-                       // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
-                       // but it does little harm and might be needed for write callers loading a User.
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
-                               $tables[] = 'actor';
-                               $fields[] = 'actor_id';
-                               $joinConds['actor'] = [
-                                       ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
-                                       [ 'actor_user = user_id' ]
-                               ];
-                       }
+                       $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id', 'actor_id' ];
+                       $joinConds = [
+                               'actor' => [ 'JOIN', 'actor_user = user_id' ],
+                       ];
 
                        $comment = __METHOD__;
                        if ( strval( $caller ) !== '' ) {
@@ -127,9 +116,7 @@ class UserCache {
                                $this->cache[$userId]['name'] = $row->user_name;
                                $this->cache[$userId]['real_name'] = $row->user_real_name;
                                $this->cache[$userId]['registration'] = $row->user_registration;
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
-                                       $this->cache[$userId]['actor'] = $row->actor_id;
-                               }
+                               $this->cache[$userId]['actor'] = $row->actor_id;
                                $usersToCheck[$userId] = $row->user_name;
                        }
                }
index 88a7042..a79325a 100644 (file)
@@ -27,18 +27,20 @@ use Wikimedia\Rdbms\DBQueryError;
  * This will work on any MediaWiki installation.
  */
 class LCStoreDB implements LCStore {
-       /** @var string */
-       private $currentLang;
-       /** @var bool */
-       private $writesDone = false;
+       /** @var string Language code */
+       private $code;
+       /** @var array Server configuration map */
+       private $server;
+
+       /** @var array Rows buffered for insertion */
+       private $batch = [];
+
        /** @var IDatabase|null */
        private $dbw;
-       /** @var array */
-       private $batch = [];
-       /** @var bool */
+       /** @var bool Whether a batch of writes were recently written */
+       private $writesDone = false;
+       /** @var bool Whether the DB is read-only or otherwise unavailable for writes */
        private $readOnly = false;
-       /** @var array Server configuration map */
-       private $server;
 
        public function __construct( $params ) {
                $this->server = $params['server'] ?? [];
@@ -74,14 +76,14 @@ class LCStoreDB implements LCStore {
                $dbw = $this->getWriteConnection();
                $this->readOnly = $dbw->isReadOnly();
 
-               $this->currentLang = $code;
+               $this->code = $code;
                $this->batch = [];
        }
 
        public function finishWrite() {
                if ( $this->readOnly ) {
                        return;
-               } elseif ( is_null( $this->currentLang ) ) {
+               } elseif ( is_null( $this->code ) ) {
                        throw new MWException( __CLASS__ . ': must call startWrite() before finishWrite()' );
                }
 
@@ -91,7 +93,7 @@ class LCStoreDB implements LCStore {
                        $dbw = $this->getWriteConnection();
                        $dbw->startAtomic( __METHOD__ );
                        try {
-                               $dbw->delete( 'l10n_cache', [ 'lc_lang' => $this->currentLang ], __METHOD__ );
+                               $dbw->delete( 'l10n_cache', [ 'lc_lang' => $this->code ], __METHOD__ );
                                foreach ( array_chunk( $this->batch, 500 ) as $rows ) {
                                        $dbw->insert( 'l10n_cache', $rows, __METHOD__ );
                                }
@@ -108,21 +110,21 @@ class LCStoreDB implements LCStore {
                        $trxProfiler->setSilenced( $oldSilenced );
                }
 
-               $this->currentLang = null;
+               $this->code = null;
                $this->batch = [];
        }
 
        public function set( $key, $value ) {
                if ( $this->readOnly ) {
                        return;
-               } elseif ( is_null( $this->currentLang ) ) {
+               } elseif ( is_null( $this->code ) ) {
                        throw new MWException( __CLASS__ . ': must call startWrite() before set()' );
                }
 
                $dbw = $this->getWriteConnection();
 
                $this->batch[] = [
-                       'lc_lang' => $this->currentLang,
+                       'lc_lang' => $this->code,
                        'lc_key' => $key,
                        'lc_value' => $dbw->encodeBlob( serialize( $value ) )
                ];
index 1d590d9..e184825 100644 (file)
@@ -221,55 +221,6 @@ class RecentChange implements Taggable {
                }
        }
 
-       /**
-        * Return the list of recentchanges fields that should be selected to create
-        * a new recentchanges object.
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return array
-        */
-       public static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->rc_user or $row->rc_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               return [
-                       'rc_id',
-                       'rc_timestamp',
-                       'rc_user',
-                       'rc_user_text',
-                       'rc_actor' => 'NULL',
-                       'rc_namespace',
-                       'rc_title',
-                       'rc_minor',
-                       'rc_bot',
-                       'rc_new',
-                       'rc_cur_id',
-                       'rc_this_oldid',
-                       'rc_last_oldid',
-                       'rc_type',
-                       'rc_source',
-                       'rc_patrolled',
-                       'rc_ip',
-                       'rc_old_len',
-                       'rc_new_len',
-                       'rc_deleted',
-                       'rc_logid',
-                       'rc_log_type',
-                       'rc_log_action',
-                       'rc_params',
-               ] + CommentStore::getStore()->getFields( 'rc_comment' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new recentchanges object.
index 9ee000d..ba6cb2c 100644 (file)
@@ -524,9 +524,7 @@ class ChangeTags {
                                        ->userHasRight( $user, 'applychangetags' )
                        ) {
                                return Status::newFatal( 'tags-apply-no-permission' );
-                       } elseif ( $user->getBlock() ) {
-                               // @TODO Ensure that the block does not apply to the `applychangetags`
-                               //       right.
+                       } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
                                return Status::newFatal( 'tags-apply-blocked', $user->getName() );
                        }
                }
@@ -601,9 +599,7 @@ class ChangeTags {
                                        ->userHasRight( $user, 'changetags' )
                        ) {
                                return Status::newFatal( 'tags-update-no-permission' );
-                       } elseif ( $user->getBlock() ) {
-                               // @TODO Ensure that the block does not apply to the `changetags`
-                               //       right.
+                       } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
                                return Status::newFatal( 'tags-update-blocked', $user->getName() );
                        }
                }
@@ -1023,9 +1019,7 @@ class ChangeTags {
                                        ->userHasRight( $user, 'managechangetags' )
                        ) {
                                return Status::newFatal( 'tags-manage-no-permission' );
-                       } elseif ( $user->getBlock() ) {
-                               // @TODO Ensure that the block does not apply to the `managechangetags`
-                               //       right.
+                       } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
                                return Status::newFatal( 'tags-manage-blocked', $user->getName() );
                        }
                }
@@ -1099,9 +1093,7 @@ class ChangeTags {
                                        ->userHasRight( $user, 'managechangetags' )
                        ) {
                                return Status::newFatal( 'tags-manage-no-permission' );
-                       } elseif ( $user->getBlock() ) {
-                               // @TODO Ensure that the block does not apply to the `managechangetags`
-                               //       right.
+                       } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
                                return Status::newFatal( 'tags-manage-blocked', $user->getName() );
                        }
                }
@@ -1200,9 +1192,7 @@ class ChangeTags {
                                        ->userHasRight( $user, 'managechangetags' )
                        ) {
                                return Status::newFatal( 'tags-manage-no-permission' );
-                       } elseif ( $user->getBlock() ) {
-                               // @TODO Ensure that the block does not apply to the `managechangetags`
-                               //       right.
+                       } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
                                return Status::newFatal( 'tags-manage-blocked', $user->getName() );
                        }
                }
@@ -1322,9 +1312,7 @@ class ChangeTags {
                                        ->userHasRight( $user, 'deletechangetags' )
                        ) {
                                return Status::newFatal( 'tags-delete-no-permission' );
-                       } elseif ( $user->getBlock() ) {
-                               // @TODO Ensure that the block does not apply to the `deletechangetags`
-                               //       right.
+                       } elseif ( $user->getBlock() && $user->getBlock()->isSitewide() ) {
                                return Status::newFatal( 'tags-manage-blocked', $user->getName() );
                        }
                }
diff --git a/includes/composer/ComposerPhpunitXmlCoverageEdit.php b/includes/composer/ComposerPhpunitXmlCoverageEdit.php
new file mode 100644 (file)
index 0000000..7db4b11
--- /dev/null
@@ -0,0 +1,60 @@
+<?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.
+ *
+ */
+
+/**
+ * Edit phpunit.xml to speed up code coverage generation.
+ *
+ * Usage: composer phpunit:coverage-edit -- extensions/ExtensionName
+ *
+ * This class runs *outside* of the normal MediaWiki
+ * environment and cannot depend upon any MediaWiki
+ * code.
+ */
+class ComposerPhpunitXmlCoverageEdit {
+
+       public static function onEvent( $event ) {
+               $IP = dirname( dirname( __DIR__ ) );
+               // TODO: Support passing arbitrary directories for core (or extensions/skins).
+               $args = $event->getArguments();
+               if ( count( $args ) !== 1 ) {
+                       throw new InvalidArgumentException( 'Pass extensions/$extensionName as an argument, ' .
+                               'e.g. "composer phpunit:coverage-edit -- extensions/BoilerPlate"' );
+               }
+               $project = current( $args );
+               $phpunitXml = \PHPUnit\Util\Xml::loadFile( $IP . '/phpunit.xml.dist' );
+               $whitelist = iterator_to_array( $phpunitXml->getElementsByTagName( 'whitelist' ) );
+               /** @var DOMNode $childNode */
+               foreach ( $whitelist as $childNode ) {
+                       $childNode->parentNode->removeChild( $childNode );
+               }
+               $whitelistElement = $phpunitXml->createElement( 'whitelist' );
+               $whitelistElement->setAttribute( 'addUncoveredFilesFromWhitelist', 'false' );
+               // TODO: Use AutoloadClasses from extension.json to load the relevant directories
+               foreach ( [ 'includes', 'src', 'maintenance' ] as $dir ) {
+                       $dirElement = $phpunitXml->createElement( 'directory', $project . '/' . $dir );
+                       $dirElement->setAttribute( 'suffix', '.php' );
+                       $whitelistElement->appendChild( $dirElement );
+
+               }
+               $phpunitXml->getElementsByTagName( 'filter' )->item( 0 )
+                       ->appendChild( $whitelistElement );
+               $phpunitXml->formatOutput = true;
+               $phpunitXml->save( $IP . '/phpunit.xml' );
+       }
+}
index 696bbf4..bd174b2 100644 (file)
@@ -66,7 +66,8 @@ class ConfigFactory implements SalvageableService {
        public function salvage( SalvageableService $other ) {
                Assert::parameterType( self::class, $other, '$other' );
 
-               /** @var ConfigFactory $other */
+               /** @var self $other */
+               '@phan-var self $other';
                foreach ( $other->factoryFunctions as $name => $otherFunc ) {
                        if ( !isset( $this->factoryFunctions[$name] ) ) {
                                continue;
index d48eb0e..ceb3944 100644 (file)
@@ -188,6 +188,8 @@ class ConfigRepository implements SalvageableService {
         */
        public function salvage( SalvageableService $other ) {
                Assert::parameterType( self::class, $other, '$other' );
+               /** @var self $other */
+               '@phan-var self $other';
 
                foreach ( $other->configItems['public'] as $name => $otherConfig ) {
                        if ( isset( $this->configItems['public'][$name] ) ) {
index e6a856c..cbcaba1 100644 (file)
@@ -81,6 +81,12 @@ class RequestContext implements IContextSource, MutableContext {
         */
        private static $instance = null;
 
+       /**
+        * Boolean flag to guard against recursion in getLanguage
+        * @var bool
+        */
+       private $languageRecursion = false;
+
        /**
         * @param Config $config
         */
@@ -318,7 +324,7 @@ class RequestContext implements IContextSource, MutableContext {
         * @since 1.19
         */
        public function getLanguage() {
-               if ( isset( $this->recursion ) ) {
+               if ( $this->languageRecursion === true ) {
                        trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING );
                        $e = new Exception;
                        wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
@@ -326,7 +332,7 @@ class RequestContext implements IContextSource, MutableContext {
                        $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
                        $this->lang = Language::factory( $code );
                } elseif ( $this->lang === null ) {
-                       $this->recursion = true;
+                       $this->languageRecursion = true;
 
                        try {
                                $request = $this->getRequest();
@@ -348,7 +354,7 @@ class RequestContext implements IContextSource, MutableContext {
                                        $this->lang = $obj;
                                }
                        } finally {
-                               unset( $this->recursion );
+                               $this->languageRecursion = false;
                        }
                }
 
index e099b38..09314ed 100644 (file)
@@ -32,24 +32,24 @@ use Wikimedia\Rdbms\ILoadBalancer;
  * @author Daniel Kinzler
  */
 abstract class DBAccessBase implements IDBAccessObject {
-       /**
-        * @var string|bool $wiki The target wiki's name. This must be an ID
-        * that LBFactory can understand.
-        */
-       protected $wiki = false;
+       /** @var ILoadBalancer */
+       private $lb;
+
+       /** @var string|bool The target wiki's DB domain */
+       protected $dbDomain = false;
 
        /**
-        * @param string|bool $wiki The target wiki's name. This must be an ID
-        * that LBFactory can understand.
+        * @param string|bool $dbDomain The target wiki's DB domain
         */
-       public function __construct( $wiki = false ) {
-               $this->wiki = $wiki;
+       public function __construct( $dbDomain = false ) {
+               $this->dbDomain = $dbDomain;
+               $this->lb = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
+                       ->getMainLB( $dbDomain );
        }
 
        /**
         * Returns a database connection.
         *
-        * @see wfGetDB()
         * @see LoadBalancer::getConnection()
         *
         * @since 1.21
@@ -60,9 +60,7 @@ abstract class DBAccessBase implements IDBAccessObject {
         * @return IDatabase
         */
        protected function getConnection( $id, array $groups = [] ) {
-               $loadBalancer = $this->getLoadBalancer();
-
-               return $loadBalancer->getConnection( $id, $groups, $this->wiki );
+               return $this->getLoadBalancer()->getConnectionRef( $id, $groups, $this->dbDomain );
        }
 
        /**
@@ -73,12 +71,10 @@ abstract class DBAccessBase implements IDBAccessObject {
         * @since 1.21
         *
         * @param IDatabase $db The database connection to release.
+        * @deprecated Since 1.34
         */
        protected function releaseConnection( IDatabase $db ) {
-               if ( $this->wiki !== false ) {
-                       $loadBalancer = $this->getLoadBalancer();
-                       $loadBalancer->reuseConnection( $db );
-               }
+               // no-op
        }
 
        /**
@@ -90,8 +86,7 @@ abstract class DBAccessBase implements IDBAccessObject {
         *
         * @return ILoadBalancer The database load balancer object
         */
-       public function getLoadBalancer() {
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-               return $lbFactory->getMainLB( $this->wiki );
+       protected function getLoadBalancer() {
+               return $this->lb;
        }
 }
index 5a5e507..a48faf1 100644 (file)
@@ -10,10 +10,16 @@ use Psr\Log\AbstractLogger;
  * goal.
  */
 class ConsoleLogger extends AbstractLogger {
+       /**
+        * @param string $channel
+        */
        public function __construct( $channel ) {
                $this->channel = $channel;
        }
 
+       /**
+        * @inheritDoc
+        */
        public function log( $level, $message, array $context = [] ) {
                fwrite( STDERR, "[$level] " .
                        LegacyLogger::format( $this->channel, $message, $context ) );
index 66ce9a3..ddffaa3 100644 (file)
@@ -39,8 +39,9 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function merge( MergeableUpdate $update ) {
-               /** @var CdnCacheUpdate $update */
+               /** @var self $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var self $update';
 
                $this->urls = array_merge( $this->urls, $update->urls );
        }
index 1691da2..d1b592d 100644 (file)
@@ -41,8 +41,9 @@ class JobQueueEnqueueUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function merge( MergeableUpdate $update ) {
-               /** @var JobQueueEnqueueUpdate $update */
+               /** @var self $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var self $update';
 
                foreach ( $update->jobsByDomain as $domain => $jobs ) {
                        $this->jobsByDomain[$domain] = $this->jobsByDomain[$domain] ?? [];
index 74e236f..8345ee6 100644 (file)
@@ -125,7 +125,7 @@ class LinksUpdate extends DataUpdate {
 
                if ( !$this->mId ) {
                        // NOTE: subclasses may initialize mId before calling this constructor!
-                       $this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE );
+                       $this->mId = $title->getArticleID( Title::READ_LATEST );
                }
 
                if ( !$this->mId ) {
index c499d08..7f56a36 100644 (file)
@@ -42,8 +42,9 @@ class MessageCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function merge( MergeableUpdate $update ) {
-               /** @var MessageCacheUpdate $update */
+               /** @var self $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var self $update';
 
                foreach ( $update->replacements as $code => $messages ) {
                        $this->replacements[$code] = array_merge( $this->replacements[$code] ?? [], $messages );
index 11e9337..dbd7c50 100644 (file)
@@ -56,6 +56,7 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
        public function merge( MergeableUpdate $update ) {
                /** @var SiteStatsUpdate $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var SiteStatsUpdate $update';
 
                foreach ( self::$counters as $field ) {
                        $this->$field += $update->$field;
index 687dfbe..4333c94 100644 (file)
@@ -46,6 +46,7 @@ class UserEditCountUpdate implements DeferrableUpdate, MergeableUpdate {
        public function merge( MergeableUpdate $update ) {
                /** @var UserEditCountUpdate $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var UserEditCountUpdate $update';
 
                foreach ( $update->infoByUser as $userId => $info ) {
                        if ( !isset( $this->infoByUser[$userId] ) ) {
index b8697e5..8a5caa2 100644 (file)
@@ -544,7 +544,7 @@ class DifferenceEngine extends ContextSource {
                        if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
                                'edit', $user, $this->mNewPage
                        ) ) {
-                               if ( $this->mNewRev->isCurrent() && $permissionManager->userCan(
+                               if ( $this->mNewRev->isCurrent() && $permissionManager->quickUserCan(
                                        'rollback', $user, $this->mNewPage
                                ) ) {
                                        $rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext(),
@@ -1511,10 +1511,6 @@ class DifferenceEngine extends ContextSource {
        private function userCanEdit( Revision $rev ) {
                $user = $this->getUser();
 
-               if ( !$rev->getContentHandler()->supportsDirectEditing() ) {
-                       return false;
-               }
-
                if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
                        return false;
                }
index 4db351b..264eabc 100644 (file)
@@ -26,6 +26,7 @@ use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBConnRef;
 use Wikimedia\Rdbms\MaintainableDBConnRef;
 use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\DBUnexpectedError;
 
 /**
  * DB accessible external objects.
@@ -113,7 +114,11 @@ class ExternalStoreDB extends ExternalStoreMedium {
         */
        public function store( $location, $data ) {
                $dbw = $this->getMaster( $location );
-               $dbw->insert( $this->getTable( $dbw ), [ 'blob_text' => $data ], __METHOD__ );
+               $dbw->insert(
+                       $this->getTable( $dbw, $location ),
+                       [ 'blob_text' => $data ],
+                       __METHOD__
+               );
                $id = $dbw->insertId();
                if ( !$id ) {
                        throw new MWException( __METHOD__ . ': no insert ID' );
@@ -149,10 +154,11 @@ class ExternalStoreDB extends ExternalStoreMedium {
        /**
         * Get a replica DB connection for the specified cluster
         *
+        * @since 1.34
         * @param string $cluster Cluster name
         * @return DBConnRef
         */
-       public function getSlave( $cluster ) {
+       public function getReplica( $cluster ) {
                $lb = $this->getLoadBalancer( $cluster );
 
                return $lb->getConnectionRef(
@@ -163,6 +169,17 @@ class ExternalStoreDB extends ExternalStoreMedium {
                );
        }
 
+       /**
+        * Get a replica DB connection for the specified cluster
+        *
+        * @param string $cluster Cluster name
+        * @return DBConnRef
+        * @deprecated since 1.34
+        */
+       public function getSlave( $cluster ) {
+               return $this->getReplica( $cluster );
+       }
+
        /**
         * Get a master database connection for the specified cluster
         *
@@ -211,15 +228,55 @@ class ExternalStoreDB extends ExternalStoreMedium {
         * Get the 'blobs' table name for this database
         *
         * @param IDatabase $db
+        * @param string|null $cluster Cluster name
         * @return string Table name ('blobs' by default)
         */
-       public function getTable( $db ) {
-               $table = $db->getLBInfo( 'blobs table' );
-               if ( is_null( $table ) ) {
-                       $table = 'blobs';
+       public function getTable( $db, $cluster = null ) {
+               if ( $cluster !== null ) {
+                       $lb = $this->getLoadBalancer( $cluster );
+                       $info = $lb->getServerInfo( $lb->getWriterIndex() );
+                       if ( isset( $info['blobs table'] ) ) {
+                               return $info['blobs table'];
+                       }
                }
 
-               return $table;
+               return $db->getLBInfo( 'blobs table' ) ?? 'blobs'; // b/c
+       }
+
+       /**
+        * Create the appropriate blobs table on this cluster
+        *
+        * @see getTable()
+        * @since 1.34
+        * @param string $cluster
+        */
+       public function initializeTable( $cluster ) {
+               global $IP;
+
+               static $supportedTypes = [ 'mysql', 'sqlite' ];
+
+               $dbw = $this->getMaster( $cluster );
+               if ( !in_array( $dbw->getType(), $supportedTypes, true ) ) {
+                       throw new DBUnexpectedError( $dbw, "RDBMS type '{$dbw->getType()}' not supported." );
+               }
+
+               $sqlFilePath = "$IP/maintenance/storage/blobs.sql";
+               $sql = file_get_contents( $sqlFilePath );
+               if ( $sql === false ) {
+                       throw new RuntimeException( "Failed to read '$sqlFilePath'." );
+               }
+
+               $rawTable = $this->getTable( $dbw, $cluster ); // e.g. "blobs_cluster23"
+               $encTable = $dbw->tableName( $rawTable );
+               $dbw->query(
+                       str_replace(
+                               [ '/*$wgDBprefix*/blobs', '/*_*/blobs' ],
+                               [ $encTable, $encTable ],
+                               $sql
+                       ),
+                       __METHOD__,
+                       $dbw::QUERY_IGNORE_DBO_TRX
+               );
        }
 
        /**
@@ -251,15 +308,23 @@ class ExternalStoreDB extends ExternalStoreMedium {
 
                $this->logger->debug( "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
 
-               $dbr = $this->getSlave( $cluster );
-               $ret = $dbr->selectField( $this->getTable( $dbr ),
-                       'blob_text', [ 'blob_id' => $id ], __METHOD__ );
+               $dbr = $this->getReplica( $cluster );
+               $ret = $dbr->selectField(
+                       $this->getTable( $dbr, $cluster ),
+                       'blob_text',
+                       [ 'blob_id' => $id ],
+                       __METHOD__
+               );
                if ( $ret === false ) {
                        $this->logger->info( "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
                        // Try the master
                        $dbw = $this->getMaster( $cluster );
-                       $ret = $dbw->selectField( $this->getTable( $dbw ),
-                               'blob_text', [ 'blob_id' => $id ], __METHOD__ );
+                       $ret = $dbw->selectField(
+                               $this->getTable( $dbw, $cluster ),
+                               'blob_text',
+                               [ 'blob_id' => $id ],
+                               __METHOD__
+                       );
                        if ( $ret === false ) {
                                $this->logger->error( "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
                        }
@@ -283,9 +348,9 @@ class ExternalStoreDB extends ExternalStoreMedium {
         *   Unlocated ids are not represented
         */
        private function batchFetchBlobs( $cluster, array $ids ) {
-               $dbr = $this->getSlave( $cluster );
+               $dbr = $this->getReplica( $cluster );
                $res = $dbr->select(
-                       $this->getTable( $dbr ),
+                       $this->getTable( $dbr, $cluster ),
                        [ 'blob_id', 'blob_text' ],
                        [ 'blob_id' => array_keys( $ids ) ],
                        __METHOD__
@@ -302,7 +367,8 @@ class ExternalStoreDB extends ExternalStoreMedium {
                        );
                        // Try the master
                        $dbw = $this->getMaster( $cluster );
-                       $res = $dbw->select( $this->getTable( $dbr ),
+                       $res = $dbw->select(
+                               $this->getTable( $dbr, $cluster ),
                                [ 'blob_id', 'blob_text' ],
                                [ 'blob_id' => array_keys( $ids ) ],
                                __METHOD__ );
index 6a3e819..17fa146 100644 (file)
@@ -213,50 +213,6 @@ class ArchivedFile {
                return $file;
        }
 
-       /**
-        * Fields in the filearchive table
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return string[]
-        */
-       static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->fa_user or $row->fa_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               wfDeprecated( __METHOD__, '1.31' );
-               return [
-                       'fa_id',
-                       'fa_name',
-                       'fa_archive_name',
-                       'fa_storage_key',
-                       'fa_storage_group',
-                       'fa_size',
-                       'fa_bits',
-                       'fa_width',
-                       'fa_height',
-                       'fa_metadata',
-                       'fa_media_type',
-                       'fa_major_mime',
-                       'fa_minor_mime',
-                       'fa_user',
-                       'fa_user_text',
-                       'fa_actor' => 'NULL',
-                       'fa_timestamp',
-                       'fa_deleted',
-                       'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
-                       'fa_sha1',
-               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'fa_description' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new archivedfile object.
index 3090632..ceb8dda 100644 (file)
@@ -202,44 +202,6 @@ class LocalFile extends File {
                }
        }
 
-       /**
-        * Fields in the image table
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return string[]
-        */
-       static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->img_user or $row->img_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               return [
-                       'img_name',
-                       'img_size',
-                       'img_width',
-                       'img_height',
-                       'img_metadata',
-                       'img_bits',
-                       'img_media_type',
-                       'img_major_mime',
-                       'img_minor_mime',
-                       'img_user',
-                       'img_user_text',
-                       'img_actor' => 'NULL',
-                       'img_timestamp',
-                       'img_sha1',
-               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new localfile object.
@@ -1449,8 +1411,6 @@ class LocalFile extends File {
                $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
                $createNullRevision = true, $revert = false
        ) {
-               global $wgActorTableSchemaMigrationStage;
-
                if ( is_null( $user ) ) {
                        global $wgUser;
                        $user = $wgUser;
@@ -1553,40 +1513,10 @@ class LocalFile extends File {
                                'oi_major_mime' => 'img_major_mime',
                                'oi_minor_mime' => 'img_minor_mime',
                                'oi_sha1' => 'img_sha1',
+                               'oi_actor' => 'img_actor',
                        ];
                        $joins = [];
 
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                               $fields['oi_user'] = 'img_user';
-                               $fields['oi_user_text'] = 'img_user_text';
-                       }
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $fields['oi_actor'] = 'img_actor';
-                       }
-
-                       if (
-                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
-                       ) {
-                               // Upgrade any rows that are still old-style. Otherwise an upgrade
-                               // might be missed if a deletion happens while the migration script
-                               // is running.
-                               $res = $dbw->select(
-                                       [ 'image' ],
-                                       [ 'img_name', 'img_user', 'img_user_text' ],
-                                       [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
-                                       $dbw->update(
-                                               'image',
-                                               [ 'img_actor' => $actorId ],
-                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
-                                               __METHOD__
-                                       );
-                               }
-                       }
-
                        # (T36993) Note: $oldver can be empty here, if the previous
                        # version of the file was broken. Allow registration of the new
                        # version to continue anyway, because that's better than having
index 61faa09..85988f6 100644 (file)
@@ -178,8 +178,6 @@ class LocalFileDeleteBatch {
        }
 
        protected function doDBInserts() {
-               global $wgActorTableSchemaMigrationStage;
-
                $now = time();
                $dbw = $this->file->repo->getMasterDB();
 
@@ -225,7 +223,8 @@ class LocalFileDeleteBatch {
                                'fa_minor_mime' => 'img_minor_mime',
                                'fa_description_id' => 'img_description_id',
                                'fa_timestamp' => 'img_timestamp',
-                               'fa_sha1' => 'img_sha1'
+                               'fa_sha1' => 'img_sha1',
+                               'fa_actor' => 'img_actor',
                        ];
                        $joins = [];
 
@@ -234,37 +233,6 @@ class LocalFileDeleteBatch {
                                $commentStore->insert( $dbw, 'fa_deleted_reason', $this->reason )
                        );
 
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                               $fields['fa_user'] = 'img_user';
-                               $fields['fa_user_text'] = 'img_user_text';
-                       }
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $fields['fa_actor'] = 'img_actor';
-                       }
-
-                       if (
-                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
-                       ) {
-                               // Upgrade any rows that are still old-style. Otherwise an upgrade
-                               // might be missed if a deletion happens while the migration script
-                               // is running.
-                               $res = $dbw->select(
-                                       [ 'image' ],
-                                       [ 'img_name', 'img_user', 'img_user_text' ],
-                                       [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
-                                       $dbw->update(
-                                               'image',
-                                               [ 'img_actor' => $actorId ],
-                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
-                                               __METHOD__
-                                       );
-                               }
-                       }
-
                        $dbw->insertSelect( 'filearchive', $tables, $fields,
                                [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
                }
index 584e001..f5b7d43 100644 (file)
@@ -106,46 +106,6 @@ class OldLocalFile extends LocalFile {
                }
        }
 
-       /**
-        * Fields in the oldimage table
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return string[]
-        */
-       static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->oi_user or $row->oi_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               return [
-                       'oi_name',
-                       'oi_archive_name',
-                       'oi_size',
-                       'oi_width',
-                       'oi_height',
-                       'oi_metadata',
-                       'oi_bits',
-                       'oi_media_type',
-                       'oi_major_mime',
-                       'oi_minor_mime',
-                       'oi_user',
-                       'oi_user_text',
-                       'oi_actor' => 'NULL',
-                       'oi_timestamp',
-                       'oi_deleted',
-                       'oi_sha1',
-               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'oi_description' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new oldlocalfile object.
index 91c6e6a..048abbb 100644 (file)
@@ -41,7 +41,7 @@ abstract class HTMLFormField {
         * the input object itself.  It should not implement the surrounding
         * table cells/rows, or labels/help messages.
         *
-        * @param string $value The value to set the input to; eg a default
+        * @param mixed $value The value to set the input to; eg a default
         *     text for a text input.
         *
         * @return string Valid HTML.
index 595b71e..8e51858 100644 (file)
@@ -77,7 +77,6 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
         * mParams['columns'] is an array with column labels as keys and column tags as values.
         *
         * @param array $value Array of the options that should be checked
-        * @suppress PhanParamSignatureMismatch
         *
         * @return string
         */
index e6936cb..2f8f5dd 100644 (file)
@@ -30,6 +30,9 @@ use MediaWiki\MediaWikiServices;
  * @ingroup SpecialPage
  */
 class ImportStreamSource implements ImportSource {
+       /**
+        * @param resource $handle
+        */
        function __construct( $handle ) {
                $this->mHandle = $handle;
        }
index 85983b1..fdd1f77 100644 (file)
@@ -32,6 +32,9 @@
  * @ingroup SpecialPage
  */
 class ImportStringSource implements ImportSource {
+       /**
+        * @param string $string
+        */
        function __construct( $string ) {
                $this->mString = $string;
                $this->mRead = false;
index 424c9d7..0ff34b0 100644 (file)
@@ -120,7 +120,11 @@ class CliInstaller extends Installer {
                        }
                        $this->setVar( '_Extensions', $status->value );
                } elseif ( isset( $options['with-extensions'] ) ) {
-                       $this->setVar( '_Extensions', array_keys( $this->findExtensions() ) );
+                       $status = $this->findExtensions();
+                       if ( !$status->isOK() ) {
+                               throw new InstallException( $status );
+                       }
+                       $this->setVar( '_Extensions', array_keys( $status->value ) );
                }
 
                // Set up the default skins
@@ -131,7 +135,11 @@ class CliInstaller extends Installer {
                        }
                        $skins = $status->value;
                } else {
-                       $skins = array_keys( $this->findExtensions( 'skins' ) );
+                       $status = $this->findExtensions( 'skins' );
+                       if ( !$status->isOK() ) {
+                               throw new InstallException( $status );
+                       }
+                       $skins = array_keys( $status->value );
                }
                $this->setVar( '_Skins', $skins );
 
index e1df844..3412aef 100644 (file)
@@ -1308,10 +1308,7 @@ abstract class DatabaseUpdater {
         * @since 1.31
         */
        protected function migrateActors() {
-               global $wgActorTableSchemaMigrationStage;
-               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
-                       !$this->updateRowExists( 'MigrateActors' )
-               ) {
+               if ( !$this->updateRowExists( 'MigrateActors' ) ) {
                        $this->output(
                                "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
                                "databases, you may want to hit Ctrl-C and do this manually with\n" .
@@ -1401,4 +1398,43 @@ abstract class DatabaseUpdater {
                        }
                }
        }
+
+       /**
+        * Only run a function if the `actor` table does not exist
+        *
+        * The transition to the actor table is dropping several indexes (and a few
+        * fields) that old upgrades want to add. This function is used to prevent
+        * those from running to re-add things when the `actor` table exists, while
+        * still allowing them to run if this really is an upgrade from an old MW
+        * version.
+        *
+        * @since 1.34
+        * @param string|array|static $func Normally this is the string naming the method on $this to
+        *  call. It may also be an array callable. If passed $this, it's assumed to be a call from
+        *  runUpdates() with $passSelf = true: $params[0] is assumed to be the real $func and $this
+        *  is prepended to the rest of $params.
+        * @param mixed ...$params Parameters for `$func`
+        * @return mixed Whatever $func returns, or null when skipped.
+        */
+       protected function ifNoActorTable( $func, ...$params ) {
+               if ( $this->tableExists( 'actor' ) ) {
+                       return null;
+               }
+
+               // Handle $passSelf from runUpdates().
+               $passSelf = false;
+               if ( $func === $this ) {
+                       $passSelf = true;
+                       $func = array_shift( $params );
+               }
+
+               if ( !is_array( $func ) && method_exists( $this, $func ) ) {
+                       $func = [ $this, $func ];
+               } elseif ( $passSelf ) {
+                       array_unshift( $params, $this );
+               }
+
+               // @phan-suppress-next-line PhanUndeclaredInvokeInCallable Phan is confused
+               return $func( ...$params );
+       }
 }
index 6d1e211..b830b70 100644 (file)
@@ -1273,7 +1273,8 @@ abstract class Installer {
         *
         * @param string $directory Directory to search in, relative to $IP, must be either "extensions"
         *     or "skins"
-        * @return array[][] [ $extName => [ 'screenshots' => [ '...' ] ]
+        * @return Status An object containing an error list. If there were no errors, an associative
+        *     array of information about the extension can be found in $status->value.
         */
        public function findExtensions( $directory = 'extensions' ) {
                switch ( $directory ) {
@@ -1292,33 +1293,43 @@ abstract class Installer {
         *
         * @param string $type Either "extension" or "skin"
         * @param string $directory Directory to search in, relative to $IP
-        * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
+        * @return Status An object containing an error list. If there were no errors, an associative
+        *     array of information about the extension can be found in $status->value.
         */
        protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
                if ( $this->getVar( 'IP' ) === null ) {
-                       return [];
+                       return Status::newGood( [] );
                }
 
                $extDir = $this->getVar( 'IP' ) . '/' . $directory;
                if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
-                       return [];
+                       return Status::newGood( [] );
                }
 
                $dh = opendir( $extDir );
                $exts = [];
+               $status = new Status;
                while ( ( $file = readdir( $dh ) ) !== false ) {
-                       if ( !is_dir( "$extDir/$file" ) ) {
+                       // skip non-dirs and hidden directories
+                       if ( !is_dir( "$extDir/$file" ) || $file[0] === '.' ) {
                                continue;
                        }
-                       $status = $this->getExtensionInfo( $type, $directory, $file );
-                       if ( $status->isOK() ) {
-                               $exts[$file] = $status->value;
+                       $extStatus = $this->getExtensionInfo( $type, $directory, $file );
+                       if ( $extStatus->isOK() ) {
+                               $exts[$file] = $extStatus->value;
+                       } elseif ( $extStatus->hasMessage( 'config-extension-not-found' ) ) {
+                               // (T225512) The directory is not actually an extension. Downgrade to warning.
+                               $status->warning( 'config-extension-not-found', $file );
+                       } else {
+                               $status->merge( $extStatus );
                        }
                }
                closedir( $dh );
                uksort( $exts, 'strnatcasecmp' );
 
-               return $exts;
+               $status->value = $exts;
+
+               return $status;
        }
 
        /**
@@ -1419,11 +1430,16 @@ abstract class Installer {
                        } elseif ( $e->missingExtensions || $e->missingSkins ) {
                                // There's an extension missing in the dependency tree,
                                // so add those to the dependency list and try again
-                               return $this->readExtension(
+                               $status = $this->readExtension(
                                        $fullJsonFile,
                                        array_merge( $extDeps, $e->missingExtensions ),
                                        array_merge( $skinDeps, $e->missingSkins )
                                );
+                               if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
+                                       $status = Status::newFatal( 'config-extension-dependency',
+                                               basename( dirname( $fullJsonFile ) ), $status->getMessage() );
+                               }
+                               return $status;
                        }
                        // Some other kind of dependency error?
                        return Status::newFatal( 'config-extension-dependency',
index 0d516b4..64c017b 100644 (file)
@@ -101,8 +101,10 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ],
                        [ 'addTable', 'filearchive', 'patch-filearchive.sql' ],
                        [ 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ],
-                       [ 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ],
-                       [ 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'recentchanges', 'rc_ns_usertext',
+                               'patch-recentchanges-utindex.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'recentchanges', 'rc_user_text',
+                               'patch-rc_user_text-index.sql' ],
 
                        // 1.9
                        [ 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ],
@@ -130,9 +132,12 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ],
                        [ 'doCategorylinksIndicesUpdate' ],
                        [ 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ],
-                       [ 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ],
-                       [ 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ],
-                       [ 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'archive', 'usertext_timestamp',
+                               'patch-archive-user-index.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'image', 'img_usertext_timestamp',
+                               'patch-image-user-index.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'oldimage', 'oi_usertext_timestamp',
+                               'patch-oldimage-user-index.sql' ],
                        [ 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ],
                        [ 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ],
 
@@ -140,7 +145,7 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'addTable', 'protected_titles', 'patch-protected_titles.sql' ],
 
                        // 1.13
-                       [ 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ],
+                       [ 'ifNoActorTable', 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ],
                        [ 'addTable', 'page_props', 'patch-page_props.sql' ],
                        [ 'addTable', 'updatelog', 'patch-updatelog.sql' ],
                        [ 'addTable', 'category', 'patch-category.sql' ],
@@ -150,7 +155,7 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'doPopulateParentId' ],
                        [ 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ],
                        [ 'doMaybeProfilingMemoryUpdate' ],
-                       [ 'doFilearchiveIndicesUpdate' ],
+                       [ 'ifNoActorTable', 'doFilearchiveIndicesUpdate' ],
 
                        // 1.14
                        [ 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ],
@@ -163,9 +168,9 @@ class MysqlUpdater extends DatabaseUpdater {
                        // 1.16
                        [ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
                        [ 'addTable', 'log_search', 'patch-log_search.sql' ],
-                       [ 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
+                       [ 'ifNoActorTable', 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
                        # listed separately from the previous update because 1.16 was released without this update
-                       [ 'doLogUsertextPopulation' ],
+                       [ 'ifNoActorTable', 'doLogUsertextPopulation' ],
                        [ 'doLogSearchPopulation' ],
                        [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
                        [ 'dropIndex', 'change_tag', 'ct_rc_id', 'patch-change_tag-indexes.sql' ],
@@ -240,9 +245,10 @@ class MysqlUpdater extends DatabaseUpdater {
 
                        // 1.23
                        [ 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ],
-                       [ 'addIndex', 'logging', 'log_user_text_type_time',
+                       [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_type_time',
                                'patch-logging_user_text_type_time_index.sql' ],
-                       [ 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_time',
+                               'patch-logging_user_text_time_index.sql' ],
                        [ 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ],
                        [ 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ],
 
@@ -288,13 +294,14 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'doNonUniquePlTlIl' ],
                        [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
                        [ 'modifyField', 'recentchanges', 'rc_ip', 'patch-rc_ip_modify.sql' ],
-                       [ 'addIndex', 'archive', 'usertext_timestamp', 'patch-rename-ar_usertext_timestamp.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'archive', 'usertext_timestamp',
+                               'patch-rename-ar_usertext_timestamp.sql' ],
 
                        // 1.29
                        [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
                        [ 'dropIndex', 'user_groups', 'ug_user_group', 'patch-user_groups-primary-key.sql' ],
                        [ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ],
-                       [ 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
 
                        // 1.30
                        [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
@@ -377,6 +384,9 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'dropTable', 'tag_summary' ],
                        [ 'dropField', 'protected_titles', 'pt_reason', 'patch-drop-comment-fields.sql' ],
                        [ 'modifyTable', 'job', 'patch-job-params-mediumblob.sql' ],
+
+                       // 1.34
+                       [ 'dropField', 'logging', 'log_user', 'patch-drop-user-fields.sql' ],
                ];
        }
 
index 31827a1..b2c7d66 100644 (file)
@@ -110,7 +110,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ],
                        [ 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ],
                        [ 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ],
-                       [ 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ],
+                       [ 'ifNoActorTable', 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ],
                        [ 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ],
                        [ 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ],
                        [ 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
@@ -152,7 +152,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addPgField', 'revision', 'rev_content_format', 'TEXT' ],
                        [ 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ],
                        [ 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ],
-                       [ 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ],
+                       [ 'ifNoActorTable', 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ],
                        [ 'addPgField', 'logging', 'log_page', 'INTEGER' ],
                        [ 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''" ],
                        [ 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''" ],
@@ -240,7 +240,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'checkOiDeleted' ],
 
                        # New indexes
-                       [ 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ],
+                       [ 'ifNoActorTable', 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ],
                        [ 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ],
                        [ 'addPgIndex', 'ipblocks', 'ipb_parent_block_id', '(ipb_parent_block_id)' ],
                        [ 'addPgIndex', 'oldimage', 'oi_sha1', '(oi_sha1)' ],
@@ -253,7 +253,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ],
                        [ 'addPgIndex', 'watchlist', 'wl_user_notificationtimestamp',
                                '(wl_user, wl_notificationtimestamp)' ],
-                       [ 'addPgIndex', 'logging', 'logging_user_type_time',
+                       [ 'ifNoActorTable', 'addPgIndex', 'logging', 'logging_user_type_time',
                                '(log_user, log_type, log_timestamp)' ],
                        [ 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ],
                        [ 'addPgIndex', 'iwlinks', 'iwl_prefix_from_title', '(iwl_prefix, iwl_from, iwl_title)' ],
@@ -263,9 +263,10 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addPgIndex', 'job', 'job_cmd_token', '(job_cmd, job_token, job_random)' ],
                        [ 'addPgIndex', 'job', 'job_cmd_token_id', '(job_cmd, job_token, job_id)' ],
                        [ 'addPgIndex', 'filearchive', 'fa_sha1', '(fa_sha1)' ],
-                       [ 'addPgIndex', 'logging', 'logging_user_text_type_time',
+                       [ 'ifNoActorTable', 'addPgIndex', 'logging', 'logging_user_text_type_time',
                                '(log_user_text, log_type, log_timestamp)' ],
-                       [ 'addPgIndex', 'logging', 'logging_user_text_time', '(log_user_text, log_timestamp)' ],
+                       [ 'ifNoActorTable', 'addPgIndex', 'logging', 'logging_user_text_time',
+                               '(log_user_text, log_timestamp)' ],
 
                        [ 'checkIndex', 'pagelink_unique', [
                                [ 'pl_from', 'int4_ops', 'btree', 0 ],
@@ -363,30 +364,36 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'checkIwlPrefix' ],
 
                        # All FK columns should be deferred
-                       [ 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'archive', 'ar_user',
+                               'mwuser(user_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user',
                                'mwuser(user_id) ON DELETE SET NULL' ],
-                       [ 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ],
-                       [ 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'filearchive', 'fa_user',
+                               'mwuser(user_id) ON DELETE SET NULL' ],
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'image', 'img_user',
+                               'mwuser(user_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ],
-                       [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ],
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'ipblocks', 'ipb_by',
+                               'mwuser(user_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_parent_block_id',
                                'ipblocks(ipb_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ],
-                       [ 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'logging', 'log_user',
+                               'mwuser(user_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'oldimage', 'oi_name',
                                'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ],
-                       [ 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ],
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'oldimage', 'oi_user',
+                               'mwuser(user_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'page_restrictions', 'pr_page',
                                'page(page_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'protected_titles', 'pt_user',
                                'mwuser(user_id) ON DELETE SET NULL' ],
-                       [ 'changeFkeyDeferrable', 'recentchanges', 'rc_user',
+                       [ 'ifNoActorTable', 'changeFkeyDeferrable', 'recentchanges', 'rc_user',
                                'mwuser(user_id) ON DELETE SET NULL' ],
                        [ 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ],
                        [ 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ],
@@ -618,6 +625,34 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'dropDefault', 'logging', 'log_comment_id' ],
                        [ 'dropPgField', 'protected_titles', 'pt_reason' ],
                        [ 'dropDefault', 'protected_titles', 'pt_reason_id' ],
+
+                       // 1.34
+                       [ 'dropPgIndex', 'archive', 'archive_user_text' ],
+                       [ 'dropPgField', 'archive', 'ar_user' ],
+                       [ 'dropPgField', 'archive', 'ar_user_text' ],
+                       [ 'dropDefault', 'archive', 'ar_actor' ],
+                       [ 'dropPgField', 'ipblocks', 'ipb_by' ],
+                       [ 'dropPgField', 'ipblocks', 'ipb_by_text' ],
+                       [ 'dropDefault', 'ipblocks', 'ipb_by_actor' ],
+                       [ 'dropPgField', 'image', 'img_user' ],
+                       [ 'dropPgField', 'image', 'img_user_text' ],
+                       [ 'dropDefault', 'image', 'img_actor' ],
+                       [ 'dropPgField', 'oldimage', 'oi_user' ],
+                       [ 'dropPgField', 'oldimage', 'oi_user_text' ],
+                       [ 'dropDefault', 'oldimage', 'oi_actor' ],
+                       [ 'dropPgField', 'filearchive', 'fa_user' ],
+                       [ 'dropPgField', 'filearchive', 'fa_user_text' ],
+                       [ 'dropDefault', 'filearchive', 'fa_actor' ],
+                       [ 'dropPgField', 'recentchanges', 'rc_user' ],
+                       [ 'dropPgField', 'recentchanges', 'rc_user_text' ],
+                       [ 'dropDefault', 'recentchanges', 'rc_actor' ],
+                       [ 'dropPgIndex', 'logging', 'logging_user_time' ],
+                       [ 'dropPgIndex', 'logging', 'logging_user_type_time' ],
+                       [ 'dropPgIndex', 'logging', 'logging_user_text_type_time' ],
+                       [ 'dropPgIndex', 'logging', 'logging_user_text_time' ],
+                       [ 'dropPgField', 'logging', 'log_user' ],
+                       [ 'dropPgField', 'logging', 'log_user_text' ],
+                       [ 'dropDefault', 'logging', 'log_actor' ],
                ];
        }
 
index 01bb30e..b21a177 100644 (file)
@@ -71,16 +71,19 @@ class SqliteInstaller extends DatabaseInstaller {
        }
 
        public function getGlobalDefaults() {
+               global $IP;
                $defaults = parent::getGlobalDefaults();
-               if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
-                       $path = str_replace(
-                               [ '/', '\\' ],
-                               DIRECTORY_SEPARATOR,
-                               dirname( $_SERVER['DOCUMENT_ROOT'] ) . '/data'
-                       );
-
-                       $defaults['wgSQLiteDataDir'] = $path;
+               if ( !empty( $_SERVER['DOCUMENT_ROOT'] ) ) {
+                       $path = dirname( $_SERVER['DOCUMENT_ROOT'] );
+               } else {
+                       // We use $IP when unable to get $_SERVER['DOCUMENT_ROOT']
+                       $path = $IP;
                }
+               $defaults['wgSQLiteDataDir'] = str_replace(
+                       [ '/', '\\' ],
+                       DIRECTORY_SEPARATOR,
+                       $path . '/data'
+               );
                return $defaults;
        }
 
@@ -122,7 +125,7 @@ class SqliteInstaller extends DatabaseInstaller {
 
                # Try realpath() if the directory already exists
                $dir = self::realpath( $this->getVar( 'wgSQLiteDataDir' ) );
-               $result = self::dataDirOKmaybeCreate( $dir, true /* create? */ );
+               $result = self::checkDataDir( $dir );
                if ( $result->isOK() ) {
                        # Try expanding again in case we've just created it
                        $dir = self::realpath( $dir );
@@ -135,12 +138,17 @@ class SqliteInstaller extends DatabaseInstaller {
        }
 
        /**
-        * @param string $dir
-        * @param bool $create
-        * @return Status
+        * Check if the data directory is writable or can be created
+        * @param string $dir Path to the data directory
+        * @return Status Return fatal Status if $dir un-writable or no permission to create a directory
         */
-       private static function dataDirOKmaybeCreate( $dir, $create = false ) {
-               if ( !is_dir( $dir ) ) {
+       private static function checkDataDir( $dir ) : Status {
+               if ( is_dir( $dir ) ) {
+                       if ( !is_readable( $dir ) ) {
+                               return Status::newFatal( 'config-sqlite-dir-unwritable', $dir );
+                       }
+               } else {
+                       // Check the parent directory if $dir not exists
                        if ( !is_writable( dirname( $dir ) ) ) {
                                $webserverGroup = Installer::maybeGetWebserverPrimaryGroup();
                                if ( $webserverGroup !== null ) {
@@ -156,25 +164,25 @@ class SqliteInstaller extends DatabaseInstaller {
                                        );
                                }
                        }
+               }
+               return Status::newGood();
+       }
 
-                       # Called early on in the installer, later we just want to sanity check
-                       # if it's still writable
-                       if ( $create ) {
-                               Wikimedia\suppressWarnings();
-                               $ok = wfMkdirParents( $dir, 0700, __METHOD__ );
-                               Wikimedia\restoreWarnings();
-                               if ( !$ok ) {
-                                       return Status::newFatal( 'config-sqlite-mkdir-error', $dir );
-                               }
-                               # Put a .htaccess file in in case the user didn't take our advice
-                               file_put_contents( "$dir/.htaccess", "Deny from all\n" );
+       /**
+        * @param string $dir Path to the data directory
+        * @return Status Return good Status if without error
+        */
+       private static function createDataDir( $dir ) : Status {
+               if ( !is_dir( $dir ) ) {
+                       Wikimedia\suppressWarnings();
+                       $ok = wfMkdirParents( $dir, 0700, __METHOD__ );
+                       Wikimedia\restoreWarnings();
+                       if ( !$ok ) {
+                               return Status::newFatal( 'config-sqlite-mkdir-error', $dir );
                        }
                }
-               if ( !is_writable( $dir ) ) {
-                       return Status::newFatal( 'config-sqlite-dir-unwritable', $dir );
-               }
-
-               # We haven't blown up yet, fall through
+               # Put a .htaccess file in in case the user didn't take our advice
+               file_put_contents( "$dir/.htaccess", "Deny from all\n" );
                return Status::newGood();
        }
 
@@ -217,10 +225,15 @@ class SqliteInstaller extends DatabaseInstaller {
        public function setupDatabase() {
                $dir = $this->getVar( 'wgSQLiteDataDir' );
 
-               # Sanity check. We checked this before but maybe someone deleted the
-               # data dir between then and now
-               $dir_status = self::dataDirOKmaybeCreate( $dir, false /* create? */ );
-               if ( !$dir_status->isOK() ) {
+               # Sanity check (Only available in web installation). We checked this before but maybe someone
+               # deleted the data dir between then and now
+               $dir_status = self::checkDataDir( $dir );
+               if ( $dir_status->isGood() ) {
+                       $res = self::createDataDir( $dir );
+                       if ( !$res->isGood() ) {
+                               return $res;
+                       }
+               } else {
                        return $dir_status;
                }
 
index 17ced50..7c3878c 100644 (file)
@@ -47,9 +47,9 @@ class SqliteUpdater extends DatabaseUpdater {
                        // 1.16
                        [ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
                        [ 'addTable', 'log_search', 'patch-log_search.sql' ],
-                       [ 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
+                       [ 'ifNoActorTable', 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
                        # listed separately from the previous update because 1.16 was released without this update
-                       [ 'doLogUsertextPopulation' ],
+                       [ 'ifNoActorTable', 'doLogUsertextPopulation' ],
                        [ 'doLogSearchPopulation' ],
                        [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
                        [ 'dropIndex', 'change_tag', 'ct_rc_id', 'patch-change_tag-indexes.sql' ],
@@ -119,9 +119,10 @@ class SqliteUpdater extends DatabaseUpdater {
 
                        // 1.23
                        [ 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ],
-                       [ 'addIndex', 'logging', 'log_user_text_type_time',
+                       [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_type_time',
                                'patch-logging_user_text_type_time_index.sql' ],
-                       [ 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'logging', 'log_user_text_time',
+                               'patch-logging_user_text_time_index.sql' ],
                        [ 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ],
                        [ 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ],
 
@@ -159,7 +160,7 @@ class SqliteUpdater extends DatabaseUpdater {
                        // 1.29
                        [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
                        [ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ],
-                       [ 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
+                       [ 'ifNoActorTable', 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
 
                        // 1.30
                        [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
@@ -249,6 +250,15 @@ class SqliteUpdater extends DatabaseUpdater {
                        [ 'dropField', 'recentchanges', 'rc_comment', 'patch-recentchanges-drop-rc_comment.sql' ],
                        [ 'dropField', 'logging', 'log_comment', 'patch-logging-drop-log_comment.sql' ],
                        [ 'dropField', 'protected_titles', 'pt_reason', 'patch-protected_titles-drop-pt_reason.sql' ],
+
+                       // 1.34
+                       [ 'dropField', 'archive', 'ar_user', 'patch-archive-drop-ar_user.sql' ],
+                       [ 'dropField', 'ipblocks', 'ipb_by', 'patch-ipblocks-drop-ipb_by.sql' ],
+                       [ 'dropField', 'image', 'img_user', 'patch-image-drop-img_user.sql' ],
+                       [ 'dropField', 'oldimage', 'oi_user', 'patch-oldimage-drop-oi_user.sql' ],
+                       [ 'dropField', 'filearchive', 'fa_user', 'patch-filearchive-drop-fa_user.sql' ],
+                       [ 'dropField', 'recentchanges', 'rc_user', 'patch-recentchanges-drop-rc_user.sql' ],
+                       [ 'dropField', 'logging', 'log_user', 'patch-logging-drop-log_user.sql' ],
                ];
        }
 
index 2412319..7bec49a 100644 (file)
@@ -104,7 +104,8 @@ class WebInstallerOptions extends WebInstallerPage {
                        $this->getFieldsetEnd()
                );
 
-               $skins = $this->parent->findExtensions( 'skins' );
+               $skins = $this->parent->findExtensions( 'skins' )->value;
+               '@phan-var array[] $skins';
                $skinHtml = $this->getFieldsetStart( 'config-skins' );
 
                $skinNames = array_map( 'strtolower', array_keys( $skins ) );
@@ -144,7 +145,8 @@ class WebInstallerOptions extends WebInstallerPage {
                        $this->getFieldsetEnd();
                $this->addHTML( $skinHtml );
 
-               $extensions = $this->parent->findExtensions();
+               $extensions = $this->parent->findExtensions()->value;
+               '@phan-var array[] $extensions';
                $dependencyMap = [];
 
                if ( $extensions ) {
@@ -324,11 +326,16 @@ class WebInstallerOptions extends WebInstallerPage {
                return null;
        }
 
+       /**
+        * @param string $name
+        * @param array $screenshots
+        */
        private function makeScreenshotsLink( $name, $screenshots ) {
                global $wgLang;
                if ( count( $screenshots ) > 1 ) {
                        $links = [];
                        $counter = 1;
+
                        foreach ( $screenshots as $shot ) {
                                $links[] = Html::element(
                                        'a',
@@ -448,7 +455,7 @@ class WebInstallerOptions extends WebInstallerPage {
         * @return bool
         */
        public function submitSkins() {
-               $skins = array_keys( $this->parent->findExtensions( 'skins' ) );
+               $skins = array_keys( $this->parent->findExtensions( 'skins' )->value );
                $this->parent->setVar( '_Skins', $skins );
 
                if ( $skins ) {
@@ -498,7 +505,7 @@ class WebInstallerOptions extends WebInstallerPage {
                        $this->setVar( 'wgRightsIcon', '' );
                }
 
-               $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' ) );
+               $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' )->value );
                $skinsToInstall = [];
                foreach ( $skinsAvailable as $skin ) {
                        $this->parent->setVarsFromRequest( [ "skin-$skin" ] );
@@ -519,7 +526,7 @@ class WebInstallerOptions extends WebInstallerPage {
                        $retVal = false;
                }
 
-               $extsAvailable = array_keys( $this->parent->findExtensions() );
+               $extsAvailable = array_keys( $this->parent->findExtensions()->value );
                $extsToInstall = [];
                foreach ( $extsAvailable as $ext ) {
                        $this->parent->setVarsFromRequest( [ "ext-$ext" ] );
index 834c129..ccb68b9 100644 (file)
@@ -89,7 +89,7 @@
        "config-uploads-not-safe": "<strong>تحذير:</strong>  الدليل الافتراضي للمرفوعات <code>$1</code> عرضة لتنفيذ سكريبتات عشوائية،\nعلى الرغم من أن ميدياويكي يتحقق من كل الملفات المرفوعة للتهديدات الأمنية، فمن المستحسن بشدة [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security إغلاق هذه الثغرة الأمنية] قبل تمكين المرفوعات.",
        "config-no-cli-uploads-check": "<strong>تحذير:</strong> لم يتم تحديد الدليل الافتراضي للمرفوعات (<code>$1</code>) للقابلية للتأثر\nلتنفيذ برنامج تعسفي أثناء تثبيت CLI.",
        "config-brokenlibxml": "يحتوي نظامك على مجموعة من إصدارات PHP وlibxml2 ويمكن أن تسبب فسادا للبيانات في ميدياويكي وتطبيقات الويب الأخرى;\nقم بالترقية إلى libxml2 2.7.3 أو أحدث ([https://bugs.php.net/bug.php؟id=45996 تم تدقيم العلة مع PHP]); \nتم إحباط التثبيت.",
-       "config-suhosin-max-value-length": "تم تثبيت Suhosin وتقييد وسيط GET <code>length</code> إلى $1 بايت،\nسيعمل مكون ResourceLoader في ميدياويكي حول هذا الحد، لكن ذلك سيؤدي إلى انخفاض مستوى الأداء، \nإذا كان ذلك ممكنا، فيجب تعيين <code>suhosin.get.max_value_length</code> على 1024 أو أعلى في <code>php.ini</code>، وتعيين <code>$wgResourceLoaderMaxQueryLength</code> لنفس القيمة في <code>LocalSettings.php</code>.",
+       "config-suhosin-max-value-length": "تم تثبيت Suhosin وتقييد وسيط GET <code>length</code> إلى $1 بايت،\nيتطلب ميدياويكي أن يكون <code>suhosin.get.max_value_length</code> $2 على الأقل، قم بتعطيل هذا الإعداد  أو بزيادة هذه القيمة إلى $3 في <code>php.ini</code>.",
        "config-using-32bit": "<strong>تحذير:</strong> يبدو أن نظامك يعمل مع الأعداد الصحيحة 32 بت، هذا [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit لا يُنصَح به].",
        "config-db-type": "نوع قاعدة البيانات:",
        "config-db-host": "مضيف قاعدة البيانات:",
index 1b814a2..a7a9d53 100644 (file)
@@ -35,7 +35,7 @@
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] دامەزراوە",
        "config-db-type": "جۆری داتابەیس:",
        "config-db-host": "خانەخوێی داتابەیس:",
-       "config-db-name": "ناوی بنکەدراوە:",
+       "config-db-name": "ناوی بنکەدراوە (بێ دابڕین (-)):",
        "config-db-install-account": "ھەژماری بەکارھێنەری بۆ دامەزراندن",
        "config-db-username": "ناوی بەکارھێنەری بنکەدراوە:",
        "config-db-password": "تێپەڕوشەی بنکەدراوە",
@@ -56,5 +56,5 @@
        "config-install-step-done": "کرا",
        "config-help": "یارمەتی",
        "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 فێربە چۆن ڕووبەڕووى ئیمەیڵە بێزارکەرەکانی ویکییەکەت دەبیتەوە]"
+       "mainpagedocfooter": "لە [https://www.mediawiki.org/wiki/Special:MyLanguage/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 fddb6a2..b57ed3d 100644 (file)
        "config-welcome": "=== Vérifications liées à l’environnement ===\nDes vérifications de base vont maintenant être effectuées pour voir si cet environnement est adapté à l’installation de MediaWiki.\nRappelez-vous d’inclure ces informations si vous recherchez de l’aide sur la manière de terminer l’installation.",
        "config-welcome-section-copyright": "=== Droit d’auteur et conditions ===\n\n$1\n\nCe programme est un logiciel libre : vous pouvez le redistribuer ou le modifier selon les termes de la Licence Publique Générale GNU telle que publiée par la Free Software Foundation (version 2 de la Licence, ou, à votre choix, toute version ultérieure).\n\nCe programme est distribué dans l’espoir qu’il sera utile, mais '''sans aucune garantie''' : sans même les garanties implicites de '''commercialisabilité''' ou d’'''adéquation à un usage particulier'''.\nVoir la Licence Publique Générale GNU pour plus de détails.\n\nVous devriez avoir reçu [$2 une copie de la Licence Publique Générale GNU] avec ce programme ; dans le cas contraire, écrivez à la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ou [https://www.gnu.org/copyleft/gpl.html lisez-la en ligne].",
        "config-sidebar": "* [https://www.mediawiki.org Accueil MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guide de l’utilisateur]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guide de l’administrateur]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]",
-       "config-sidebar-readme": "Me lire",
+       "config-sidebar-readme": "Lisez-moi",
        "config-sidebar-relnotes": "Notes de version",
-       "config-sidebar-license": "Copie",
+       "config-sidebar-license": "Droit de copie",
        "config-sidebar-upgrade": "Mise à jour",
        "config-env-good": "L’environnement a été vérifié.\nVous pouvez installer MediaWiki.",
        "config-env-bad": "L’environnement a été vérifié.\nVous ne pouvez pas installer MediaWiki.",
        "config-env-php": "PHP $1 est installé.",
        "config-env-hhvm": "HHVM $1 est installé.",
-       "config-unicode-using-intl": "Utilisation de [https://php.net/manual/en/book.intl.php extension intl de PHP] pour la normalisation Unicode.",
-       "config-unicode-pure-php-warning": "<strong>Attention :</strong> L’[https://php.net/manual/en/book.intl.php extension intl de PHP] n’est pas disponible pour la normalisation d’Unicode, retour à la version lente implémentée en PHP seulement.\nSi votre site web sera très fréquenté, vous devriez lire ceci : [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).",
+       "config-unicode-using-intl": "Utilisation de l’[https://php.net/manual/en/book.intl.php extension intl de PHP] pour la normalisation Unicode.",
+       "config-unicode-pure-php-warning": "<strong>Attention :</strong> l’[https://php.net/manual/en/book.intl.php extension intl de PHP] n’est pas disponible pour la normalisation d’Unicode, retour à la version lente implémentée en PHP seulement.\nSi votre site web sera très fréquenté, vous devriez lire ceci : [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).",
        "config-unicode-update-warning": "<strong>Attention :</strong> la version installée du normalisateur Unicode utilise une ancienne version de la bibliothèque logicielle du [http://site.icu-project.org/ ''Projet ICU''].\nVous devriez faire une [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations mise à jour] si vous êtes concerné par l’usage d’Unicode.",
        "config-no-db": "Impossible de trouver un pilote de base de données approprié ! Vous devez installer un pilote de base de données pour PHP. {{PLURAL:$2|Le type suivant|Les types suivants}} de bases de données {{PLURAL:$2|est reconnu|sont reconnus}} : $1.\n\nSi vous avez compilé PHP vous-même, reconfigurez-le avec un client de base de données activé, par exemple en utilisant <code>./configure --with-mysqli</code>.  \nSi vous avez installé PHP depuis un paquet Debian ou Ubuntu, alors vous devrez aussi installer, par exemple, le paquet <code>php-mysql</code>.",
-       "config-outdated-sqlite": "<strong>Attention :</strong> vous avez SQLite $2, qui est inférieur à la version minimale requise $1. SQLite sera indisponible.",
-       "config-no-fts3": "<strong>Attention :</strong> SQLite est compilé sans le [//sqlite.org/fts3.html module FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
+       "config-outdated-sqlite": "<strong>Attention:</strong> vous avez SQLite $2, qui est inférieur à la version minimale requise $1. SQLite sera indisponible.",
+       "config-no-fts3": "<strong>Attention:</strong> SQLite est compilé sans le [//sqlite.org/fts3.html module FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
        "config-pcre-old": "<strong>Erreur fatale :</strong> PCRE $1 ou ultérieur est nécessaire.\nVotre binaire PHP est lié avec PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/Plus d’information sur PCRE].",
        "config-pcre-no-utf8": "<strong>Erreur fatale :</strong> le module PCRE de PHP semble être compilé sans la prise en charge de PCRE_UTF8.\nMediaWiki a besoin de la gestion d’UTF-8 pour fonctionner correctement.",
        "config-memory-raised": "Le paramètre <code>memory_limit</code> de PHP était à $1, porté à $2.",
        "config-apc": "[https://www.php.net/apc APC] est installé",
        "config-apcu": "[https://www.php.net/apcu APCu] est installé",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] est installé",
-       "config-no-cache-apcu": "<strong>Attention :</strong> impossible de trouver [https://www.php.net/apcu APCu] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa mise en cache des objets n’est pas activée.",
-       "config-mod-security": "<strong>Attention :</strong> votre serveur web a activé [https://modsecurity.org/ mod_security]/mod_security2 . Dans plusieurs configurations communes cela pose des problèmes à MediaWiki ou à d’autres applications qui permettent aux utilisateurs de publier un contenu quelconque. \nSi possible, ceci devrait être désactivé. Sinon, reportez-vous à [https://modsecurity.org/documentation/ la documentation de mod_security] ou contactez l’assistance de votre hébergeur si vous rencontrez des erreurs aléatoires.",
+       "config-no-cache-apcu": "<strong>Attention:</strong> impossible de trouver [https://www.php.net/apcu APCu] ou [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nLa mise en cache des objets n’est pas activée.",
+       "config-mod-security": "<strong>Attention :</strong> votre serveur web a activé [https://modsecurity.org/ mod_security] ou mod_security2. Dans plusieurs configurations communes cela pose des problèmes à MediaWiki ou à d’autres applications qui permettent aux utilisateurs de publier un contenu quelconque. \nSi possible, ceci devrait être désactivé. Sinon, reportez-vous à la [https://modsecurity.org/documentation/  documentation de mod_security] ou contactez l’assistance de votre hébergeur si vous rencontrez des erreurs aléatoires.",
        "config-diff3-bad": "L’utilitaire de comparaison de texte GNU diff3 est introuvable. Vous pouvez l’ignorer pour le moment, mais cela peut provoquer des conflits de modification plus souvent.",
        "config-git": "Logiciel de contrôle de version Git trouvé : <code>$1</code>.",
        "config-git-bad": "Logiciel de contrôle de version Git non trouvé. Vous pouvez l’ignorer pour le moment. Notez que Special:Version n’affichera pas les hachages de validation.",
        "config-imagemagick": "ImageMagick trouvé : <code>$1</code>.\nLa génération de vignettes d’images sera activée si vous activez les téléversements.",
-       "config-gd": "La bibliothèque graphique GD intégrée a été trouvée.\nLa miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
+       "config-gd": "La bibliothèque graphique GD intégrée a été trouvée.\nLa miniaturisation dimages sera activée si vous activez le téléversement de fichiers.",
        "config-no-scaling": "Impossible de trouver la bibliothèque GD ou ImageMagick.\nLa miniaturisation d’images sera désactivée.",
        "config-no-uri": "<strong>Erreur :</strong> impossible de déterminer l’URI du script actuel.\nInstallation interrompue.",
-       "config-no-cli-uri": "<strong>Attention :</strong> Aucun <code>--scriptpath</code> n’a été spécifié ; <code>$1</code> sera utilisé par défaut.",
+       "config-no-cli-uri": "<strong>Attention :</strong> aucun <code>--scriptpath</code> n’a été spécifié ; <code>$1</code> sera utilisé par défaut.",
        "config-using-server": "Utilisation du nom de serveur « <nowiki>$1</nowiki> ».",
-       "config-using-uri": "Utilisation de l'URL de serveur \"<nowiki>$1$2</nowiki>\".",
-       "config-uploads-not-safe": "<strong>Attention :</strong> Votre répertoire par défaut pour les téléversements, <code>$1</code>, est vulnérable, car il peut exécuter n’importe quel script.\nBien que MediaWiki vérifie tous les fichiers téléversés, il est fortement recommandé de [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security fermer cette faille de sécurité] (texte en anglais) avant d’activer les téléversements.",
-       "config-no-cli-uploads-check": "'''Attention:''' Votre répertoire par défaut pour les imports(<code>$1</code>) n'est pas contrôlé concernant la vulnérabilité d'exécution de scripts arbitraires lors de l'installation CLI.",
-       "config-brokenlibxml": "Votre système utilise une combinaison de versions de PHP et libxml2 qui est boguée et peut engendrer des corruptions cachées de données dans MediaWiki et d’autres applications web.\nVeuillez mettre à jour votre système vers libxml2 2.7.3 ou plus récent ([https://bugs.php.net/bug.php?id=45996 bogue déposé auprès de PHP]).\nInstallation interrompue.",
-       "config-suhosin-max-value-length": "Suhosin est installé et limite la <code>longueur</code> du paramètre GET à $1 octets.\nLe composant ResourceLoader de MediaWiki va répondre en respectant cette limite, mais ses performances seront dégradées. Si possible, vous devriez définir <code>suhosin.get.max_value_length</code> à 1024 ou plus dans le fichier <code>php.ini</code>, et fixer <code>$wgResourceLoaderMaxQueryLength</code> à la même valeur dans <code>LocalSettings.php</code>.",
-       "config-using-32bit": "<strong>Attention:</strong> votre système semble utiliser les entiers sur 32 bits. Ceci n'est [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit pas recommandé].",
-       "config-db-type": "Type de base de données :",
-       "config-db-host": "Nom d’hôte de la base de données :",
-       "config-db-host-help": "Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.\n\nSi vous utilisez un hébergement mutualisé, votre hébergeur doit vous avoir fourni le nom d’hôte correct dans sa documentation.\n\nSi vous utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S’il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.\n\nSi vous utilisez PostgreSQL, laissez ce champ vide pour vous connecter via un socket Unix.",
+       "config-using-uri": "Utilisation de l’URL de serveur « <nowiki>$1$2</nowiki> ».",
+       "config-uploads-not-safe": "<strong>Attention :</strong> votre répertoire par défaut pour les téléversements, <code>$1</code>, est vulnérable, car il peut exécuter n’importe quel script.\nBien que MediaWiki vérifie tous les fichiers téléversés, il est fortement recommandé de [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security fermer cette faille de sécurité] (texte en anglais) avant d’activer les téléversements.",
+       "config-no-cli-uploads-check": "'''Attention :''' votre répertoire par défaut pour les imports (<code>$1</code>) n’est pas contrôlé concernant la vulnérabilité d’exécution de scripts arbitraires lors de l’installation CLI.",
+       "config-brokenlibxml": "Votre système utilise une combinaison de versions de PHP et libxml2 qui est boguée et peut engendrer des corruptions cachées de données dans MediaWiki et d’autres applications web.\nVeuillez mettre à jour votre système vers libxml2 2.7.3 ou plus récent ([https://bugs.php.net/bug.php?id=45996 anomalie signalée auprès de PHP]).\nInstallation interrompue.",
+       "config-suhosin-max-value-length": "Suhosin est installé et limite la <code>longueur</code> de paramètre GET à $1 octets.\nMediaWiki exige que <code>suhosin.get.max_value_length</code> vaille au moins $2. Désactiver ce paramètre, ou augmenter sa valeur à $3 dans <code>php.ini</code>.",
+       "config-using-32bit": "<strong>Attention :</strong> votre système semble utiliser les entiers sur 32 bits. Ceci n’est [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit pas recommandé].",
+       "config-db-type": "Type de base de données:",
+       "config-db-host": "Nom d’hôte de la base de données:",
+       "config-db-host-help": "Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.\n\nSi vous utilisez un hébergement mutualisé, votre hébergeur doit vous avoir fourni le nom d’hôte correct dans sa documentation.\n\nSi vous utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S’il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.\n\nSi vous utilisez PostgreSQL, laissez ce champ vide pour vous connecter via un socket Unix.",
        "config-db-wiki-settings": "Identifier ce wiki",
-       "config-db-name": "Nom de la base de données (sans tirets):",
-       "config-db-name-help": "Choisissez un nom qui identifie votre wiki.\nIl ne doit pas contenir d'espaces.\n\nSi vous utilisez un hébergement web partagé, votre hébergeur vous fournira un nom spécifique de base de données à utiliser, ou bien vous permet de créer des bases de données via un panneau de contrôle.",
-       "config-db-install-account": "Compte d'utilisateur pour l'installation",
-       "config-db-username": "Nom d’utilisateur de la base de données :",
-       "config-db-password": "Mot de passe de la base de données :",
-       "config-db-install-username": "Entrez le nom d’utilisateur qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du nom d’utilisateur du compte MediaWiki, mais du nom d’utilisateur pour votre base de données.",
-       "config-db-install-password": "Entrez le mot de passe qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du mot de passe du compte MediaWiki, mais du mot de passe pour votre base de données.",
-       "config-db-install-help": "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le processus d'installation.",
-       "config-db-account-lock": "Utiliser le même nom d'utilisateur et le même mot de passe pendant le fonctionnement habituel",
-       "config-db-wiki-account": "Compte d'utilisateur pour le fonctionnement habituel",
-       "config-db-wiki-help": "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le fonctionnement habituel du wiki.\nSi le compte n'existe pas, et le compte d'installation dispose de privilèges suffisants, ce compte d'utilisateur sera créé avec les privilèges minimum requis pour faire fonctionner le wiki.",
-       "config-db-prefix": "Préfixe des tables de la base de données (sans tirets) :",
-       "config-db-prefix-help": "Si vous avez besoin de partager une base de données entre plusieurs wikis, ou entre MediaWiki et une autre application Web, vous pouvez choisir d'ajouter un préfixe à tous les noms de table pour éviter les conflits.\nNe pas utiliser d'espaces.\n\nCe champ est généralement laissé vide.",
+       "config-db-name": "Nom de la base de données (sans tirets):",
+       "config-db-name-help": "Choisissez un nom qui identifie votre wiki.\nIl ne doit pas contenir despaces.\n\nSi vous utilisez un hébergement web partagé, votre hébergeur vous fournira un nom spécifique de base de données à utiliser, ou bien vous permet de créer des bases de données via un panneau de contrôle.",
+       "config-db-install-account": "Compte d’utilisateur pour l’installation",
+       "config-db-username": "Nom d’utilisateur de la base de données:",
+       "config-db-password": "Mot de passe de la base de données:",
+       "config-db-install-username": "Entrez le nom d’utilisateur qui sera utilisé pour se connecter à la base de données pendant le processus d’installation. Il ne s’agit pas du nom d’utilisateur du compte MediaWiki (serveur web, PHP) sur le système, mais du nom d’utilisateur dans votre base de données SQL.",
+       "config-db-install-password": "Entrez le mot de passe qui sera utilisé pour se connecter à la base de données pendant le processus d’installation. Il ne s’agit pas du mot de passe du compte MediaWiki (serveur web, PHP) sur le système, mais du mot de passe dans votre base de données SQL.",
+       "config-db-install-help": "Entrez le nom d’utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le processus d’installation.",
+       "config-db-account-lock": "Utiliser le même nom d’utilisateur et le même mot de passe pour les opérations communes",
+       "config-db-wiki-account": "Compte d’utilisateur pour les opérations communes",
+       "config-db-wiki-help": "Entrez le nom d’utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le fonctionnement habituel du wiki.\nSi le compte n’existe pas, et le compte d’installation dispose de privilèges suffisants, ce compte d’utilisateur sera créé avec les privilèges minimum requis pour faire fonctionner le wiki.",
+       "config-db-prefix": "Préfixe des tables de la base de données (sans tirets):",
+       "config-db-prefix-help": "Si vous avez besoin de partager une base de données entre plusieurs wikis, ou entre MediaWiki et une autre application Web, vous pouvez choisir d’ajouter un préfixe à tous les noms de table pour éviter les conflits.\nNe pas utiliser d’espaces.\n\nCe champ est généralement laissé vide.",
        "config-mysql-old": "MySQL $1 ou version ultérieure est requis. Vous avez $2.",
-       "config-db-port": "Port de la base de données :",
-       "config-db-schema": "Schéma pour MediaWiki (sans tirets) :",
-       "config-db-schema-help": "Ce schéma est généralement correct.\nNe le changez que si vous êtes sûr que c'est nécessaire.",
-       "config-pg-test-error": "Impossible de se connecter à la base de données '''$1''' : $2",
-       "config-sqlite-dir": "Dossier des données SQLite :",
-       "config-sqlite-dir-help": "SQLite stocke toutes les données dans un fichier unique.\n\nLe répertoire que vous fournissez doit être accessible en écriture par le serveur lors de l'installation.\n\nIl '''ne faut pas''' qu'il soit accessible via le web, c'est pourquoi il n'est pas à l'endroit où sont vos fichiers PHP.\n\nL'installateur écrira un fichier <code>.htaccess</code> en même temps, mais s'il y a échec, quelqu'un peut accéder à votre base de données.\nCela comprend les données des utilisateurs (adresses de courriel, mots de passe hachés) ainsi que des révisions supprimées et d'autres données confidentielles du wiki.\n\nEnvisagez de placer la base de données ailleurs, par exemple dans <code>/var/lib/mediawiki/yourwiki</code>.",
+       "config-db-port": "Port de la base de données:",
+       "config-db-schema": "Schéma pour MediaWiki (sans tirets):",
+       "config-db-schema-help": "Ce schéma est généralement correct.\nNe le changez que si vous êtes sûr que cest nécessaire.",
+       "config-pg-test-error": "Impossible de se connecter à la base de données '''$1''': $2",
+       "config-sqlite-dir": "Dossier des données SQLite:",
+       "config-sqlite-dir-help": "SQLite stocke toutes les données dans un fichier unique.\n\nLe répertoire que vous fournissez doit être accessible en écriture par le serveur lors de l’installation.\n\nIl '''ne faut pas''' qu’il soit accessible via le web, c’est pourquoi il n’est pas à l’endroit où sont vos fichiers PHP.\n\nL’installateur écrira un fichier <code>.htaccess</code> en même temps, mais s’il y a échec, quelqu’un peut accéder à votre base de données.\nCela comprend les données des utilisateurs (adresses de courriel, mots de passe hachés) ainsi que des révisions supprimées et d’autres données confidentielles du wiki.\n\nEnvisagez de placer la base de données ailleurs, par exemple dans <code>/var/lib/mediawiki/yourwiki</code>.",
        "config-type-mysql": "MariaDB, MySQL , ou compatible",
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-missing-db-host": "Vous devez entrer une valeur pour « {{int:config-db-host}} ».",
        "config-invalid-db-name": "Nom de la base de données invalide (« $1 »).\nUtiliser seulement les lettres ASCII (a-z, A-Z), les chiffres (0-9), les caractères de soulignement (_) et les tirets (-).",
        "config-invalid-db-prefix": "Préfixe de la base de données non valide « $1 ».\nUtiliser seulement les lettres ASCII (a-z, A-Z), les chiffres (0-9), les caractères de soulignement (_) et les tirets (-).",
-       "config-connection-error": "$1.\n\nVérifier le nom d’hôte, le nom d’utilisateur et le mot de passe ci-dessous puis réessayer. Si vous utilisez « localhost » comme hôte de base de données, essayez d’utiliser « 127.0.0.1 » à la place (ou inversement).",
+       "config-connection-error": "$1.\n\nVérifier le nom d’hôte, le nom d’utilisateur et le mot de passe ci-dessous puis réessayer. Si vous utilisez « localhost » comme hôte de base de données, essayez d’utiliser « 127.0.0.1 » à la place (ou inversement).",
        "config-invalid-schema": "Schéma invalide pour MediaWiki « $1 ».\nUtiliser seulement les lettres ASCII (a-z, A-Z), les chiffres (0-9) et les caractères de soulignement (_).",
        "config-postgres-old": "PostgreSQL $1 ou version ultérieure est requis. Vous avez $2.",
        "config-sqlite-name-help": "Choisir un nom qui identifie votre wiki.\nNe pas utiliser d'espaces ni de traits d'union.\nIl sera utilisé pour le fichier de données SQLite.",
        "config-sqlite-cant-create-db": "Impossible de créer le fichier de base de données <code>$1</code>.",
        "config-sqlite-fts3-downgrade": "PHP n’a pas trouvé la prise en charge FTS3, les tables sont restreintes.",
        "config-can-upgrade": "Il y a des tables MediaWiki dans cette base de données.\nPour les mettre au niveau de MediaWiki $1, cliquez sur '''Continuer'''.",
-       "config-upgrade-error": "Une erreur est survenue durant la mise à jour des tables MédiaWiki de votre base de données.\n\nPour plus d'informations voir le journal ci-dessus, pour réessayer, cliquez sur <strong>Continuer</strong>.",
+       "config-upgrade-error": "Une erreur est survenue durant la mise à jour des tables MediaWiki de votre base de données.\n\nPour plus d'informations voir le journal ci-dessus, pour réessayer, cliquez sur <strong>Continuer</strong>.",
        "config-upgrade-done": "Mise à jour terminée.\n\nVous pouvez maintenant [$1 commencer à utiliser votre wiki].\n\nSi vous souhaitez régénérer votre fichier <code>LocalSettings.php</code>, cliquez sur le bouton ci-dessous.\nCeci '''n'est pas recommandé''' sauf si vous rencontrez des problèmes avec votre wiki.",
        "config-upgrade-done-no-regenerate": "Mise à jour terminée.\n\nVous pouvez maintenant [$1 commencer à utiliser votre wiki].",
        "config-regenerate": "Regénérer LocalSettings.php →",
        "config-ns-site-name": "Même nom que le wiki : $1",
        "config-ns-other": "Autre (préciser)",
        "config-ns-other-default": "MonWiki",
-       "config-project-namespace-help": "Suivant l’exemple de Wikipédia, plusieurs wikis gardent leurs pages de politique séparées de leurs pages de contenu, dans un '''espace de noms de niveau projet''' propre.\nTous les titres de page de cet espace de noms commence par un préfixe défini, que vous pouvez spécifier ici.\nTraditionnellement, ce préfixe est dérivé du nom du wiki, et ne peut contenir de caractères de ponctuation tels que « # » ou « : ».",
+       "config-project-namespace-help": "Suivant l’exemple de Wikipédia, plusieurs wikis gardent leurs pages de politique séparées de leurs pages de contenu, dans un '''espace de noms de niveau projet''' propre.\nTous les titres de page de cet espace de noms commence par un préfixe défini, que vous pouvez spécifier ici.\nTraditionnellement, ce préfixe est dérivé du nom du wiki, et ne peut contenir de caractères de ponctuation tels que « # » ou « : ».",
        "config-ns-invalid": "L'espace de noms spécifié « <nowiki>$1</nowiki> » n'est pas valide.\nSpécifiez un espace de noms différent pour le projet.",
        "config-ns-conflict": "L'espace de noms spécifié « <nowiki>$1</nowiki> » est en conflit avec un espace de noms par défaut de MediaWiki.\nChoisir un autre espace de noms pour le projet.",
        "config-admin-box": "Compte administrateur",
        "config-skins": "Habillages",
        "config-skins-help": "Les habillages listés ci-dessous ont été détectés dans votre répertoire <code>./skins</code>. Vous devez en activer au moins un, et choisir celui par défaut.",
        "config-skins-use-as-default": "Utiliser cet habillage par défaut",
-       "config-skins-missing": "Aucun habillage trouvé ; MédiaWiki utilisera un habillage de secours jusqu’à ce que vous en installiez un approprié.",
+       "config-skins-missing": "Aucun habillage trouvé ; MediaWiki utilisera un habillage de secours jusqu’à ce que vous en installiez un approprié.",
        "config-skins-must-enable-some": "Vous devez choisir au moins un habillage à activer.",
        "config-skins-must-enable-default": "L’habillage choisi par défaut doit être activé.",
        "config-install-alreadydone": "'''Attention''': Vous semblez avoir déjà installé MediaWiki et tentez de l'installer à nouveau.\nS'il vous plaît, allez à la page suivante.",
        "config-install-extension-tables": "Création de tables pour les extensions activées",
        "config-install-mainpage-failed": "Impossible d’insérer la page principale : $1",
        "config-install-done": "<strong>Félicitations!</strong>\nVous avez installé MediaWiki.\n\nLe programme d'installation a généré un fichier <code>LocalSettings.php</code>. Il contient tous les paramètres de votre configuration.\n\nVous devrez le télécharger et le mettre à la racine de votre installation wiki (dans le même répertoire que index.php). Le téléchargement devrait démarrer automatiquement.\n\nSi le téléchargement n'a pas été proposé, ou que vous l'avez annulé, vous pouvez redémarrer le téléchargement en cliquant ce lien :\n\n$3\n\n<strong>Note :</strong> Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera pas disponible plus tard si vous quittez l'installation sans le télécharger.\n\nLorsque c'est fait, vous pouvez <strong>[$2 accéder à votre wiki]</strong> .",
-       "config-install-done-path": "<strong>Félicitations !</strong>\nVous avez installé MédiaWiki.\n\nL’installeur a généré un fichier <code>LocalSettings.php</code>.\nIl contient toute votre configuration.\n\nVous devez le télécharger et le mettre dans <code>$4</code>. Le téléchargement devrait avoir démarré automatiquement.\n\nSi le téléchargement n’a pas été proposé ou si vous l’avez annulé, vous pouvez le redémarrer en cliquant sur le lien ci-dessous :\n\n$3\n\n<strong>Note :</strong> Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera plus disponible ultérieurement si vous quittez l’installation sans le télécharger.\n\nUne fois ceci fait, vous pouvez <strong>[$2 entrer dans votre wiki]</strong>.",
-       "config-install-success": "MédiaWiki a été installé correctement. Vous pouvez maintenant visiter <$1$2> pour voir votre wiki.\nSi vous avez des questions, consultez notre foire aux questions :\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> ou utilisez un des\nforums de support liés sur cette page.",
+       "config-install-done-path": "<strong>Félicitations !</strong>\nVous avez installé MediaWiki.\n\nL’installeur a généré un fichier <code>LocalSettings.php</code>.\nIl contient toute votre configuration.\n\nVous devez le télécharger et le mettre dans <code>$4</code>. Le téléchargement devrait avoir démarré automatiquement.\n\nSi le téléchargement n’a pas été proposé ou si vous l’avez annulé, vous pouvez le redémarrer en cliquant sur le lien ci-dessous :\n\n$3\n\n<strong>Note :</strong> Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera plus disponible ultérieurement si vous quittez l’installation sans le télécharger.\n\nUne fois ceci fait, vous pouvez <strong>[$2 entrer dans votre wiki]</strong>.",
+       "config-install-success": "MediaWiki a été installé correctement. Vous pouvez maintenant visiter <$1$2> pour voir votre wiki.\nSi vous avez des questions, consultez notre foire aux questions :\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> ou utilisez un des\nforums de support liés sur cette page.",
        "config-install-db-success": "La base de données a été bien installée",
        "config-download-localsettings": "Télécharger <code>LocalSettings.php</code>",
        "config-help": "aide",
        "config-skins-screenshots": "$1 (captures d’écran : $2)",
        "config-extensions-requires": "$1 (nécessite $2)",
        "config-screenshot": "Captures d’écrans",
-       "config-extension-not-found": "Impossible de trouver le fichier d’inscription pour l’extension « $1 »",
-       "config-extension-dependency": "Une erreur de dépendance s’est produite en installant l’extension « $1 » : $2",
+       "config-extension-not-found": "Impossible de trouver le fichier d’inscription pour l’extension « $1 »",
+       "config-extension-dependency": "Une erreur de dépendance s’est produite en installant l’extension « $1 » : $2",
        "mainpagetext": "<strong>MediaWiki a été installé.</strong>",
        "mainpagedocfooter": "Consultez le [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel de wiki.\n\n== Pour démarrer ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste des paramètres de configuration]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr Questions courantes sur MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Adaptez MediaWiki dans votre langue]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Apprendre comment lutter contre le pourriel dans votre wiki]"
 }
index eae79b0..e5ebb42 100644 (file)
        "config-restart": "Si, reinitia lo",
        "config-welcome": "=== Verificationes del ambiente ===\nVerificationes de base essera ora exequite pro determinar si iste ambiente es apte pro le installation de MediaWiki.\nNon oblida de includer iste information si tu cerca adjuta pro completar le installation.",
        "config-welcome-section-copyright": "=== Copyright and Terms ===\n\n$1\n\nIste programma es software libere; vos pote redistribuer lo e/o modificar lo sub le conditiones del Licentia Public General de GNU publicate per le Free Software Foundation; version 2 del Licentia, o (a vostre option) qualcunque version posterior.\n\nIste programma es distribuite in le sperantia que illo sia utile, ma '''sin garantia''', sin mesmo le implicite garantia de '''commercialisation''' o '''aptitude pro un proposito particular'''.\nVide le Licentia Public General de GNU pro plus detalios.\n\nVos deberea haber recipite [$2 un exemplar del Licentia Public General de GNU] con iste programma; si non, scribe al Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [https://www.gnu.org/copyleft/gpl.html lege lo in linea].",
-       "config-sidebar": "* [https://www.mediawiki.org Pagina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guida pro usatores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guida pro administratores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Lege me</doclink>\n* <doclink href=ReleaseNotes>Notas de iste version</doclink>\n* <doclink href=Copying>Conditiones de copia</doclink>\n* <doclink href=UpgradeDoc>Actualisation</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org Pagina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guida pro usatores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guida pro administratores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]",
+       "config-sidebar-readme": "Lege me",
+       "config-sidebar-relnotes": "Notas de iste version",
+       "config-sidebar-license": "Conditiones de copia",
+       "config-sidebar-upgrade": "Actualisation",
        "config-env-good": "Le ambiente ha essite verificate.\nTu pote installar MediaWiki.",
        "config-env-bad": "Le ambiente ha essite verificate.\nTu non pote installar MediaWiki.",
        "config-env-php": "PHP $1 es installate.",
@@ -79,7 +83,7 @@
        "config-uploads-not-safe": "'''Aviso:''' Le directorio predefinite pro files incargate <code>$1</code> es vulnerabile al execution arbitrari de scripts.\nBen que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
        "config-no-cli-uploads-check": "'''Attention:''' Le directorio predefinite pro files incargate (<code>$1</code>) non es verificate contra le vulnerabilitate\nal execution arbitrari de scripts durante le installation de CLI.",
        "config-brokenlibxml": "Vostre systema ha un combination de versiones de PHP e libxml2 que es defectuose e pote causar corruption celate de datos in MediaWiki e altere applicationes web.\nActualisa a libxml2 2.7.3 o plus recente ([https://bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).\nInstallation abortate.",
-       "config-suhosin-max-value-length": "Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.\nLe componente ResourceLoader de MediaWiki va contornar iste limite, ma isto prejudicara le rendimento.\nSi possibile, tu deberea mitter <code>suhosin.get.max_value_length</code> a 1024 o superior in <code>php.ini</code>, e mitter <code>$wgResourceLoaderMaxQueryLength</code> al mesme valor in <code>LocalSettings.php</code>.",
+       "config-suhosin-max-value-length": "Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.\nMediaWiki require que <code>suhosin.get.max_value_length</code> sia al minus $2. Disactiva iste parametro, o augmenta iste valor a $3 in <code>php.ini</code>.",
        "config-using-32bit": "<strong>Attention:</strong> tu systema pare operar con integres de 32 bits. Isto [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit non es recommendate].",
        "config-db-type": "Typo de base de datos:",
        "config-db-host": "Servitor de base de datos:",
index cb72d91..f385ab5 100644 (file)
        "config-uploads-not-safe": "<strong>Attenzione:</strong> la directory predefinita per i caricamenti <code>$1</code> è vulnerabile all'esecuzione arbitraria di script.\nAnche se, a difesa della sicurezza, MediaWiki controlla tutti i file caricati, è fortemente raccomandato di [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security chiudere questa minaccia] prima di abilitare i caricamenti.",
        "config-no-cli-uploads-check": "<strong>Attenzione:</strong> la directory predefinita per i caricamenti (<code>$1</code>) non è stata verificata per la vulnerabilità sull'esecuzione arbitraria di script durante l'installazione da linea di comando.",
        "config-brokenlibxml": "Il tuo sistema ha una combinazione di versioni di PHP e libxml2 che è difettosa e che può provocare un danneggiamento non visibile di dati in MediaWiki ed in altre applicazioni per il web.\nAggiorna a libxml2 2.7.3 o successivo ([https://bugs.php.net/bug.php?id=45996 il bug è studiato dal lato PHP]).\nInstallazione interrotta.",
-       "config-suhosin-max-value-length": "Suhosin è installato e limita il parametro GET <code>length</code> a $1 byte.\nIl componente MediaWiki ResourceLoader funzionerà aggirando questo limite, ma riducendo le prestazioni.\nSe possibile, dovresti impostare <code>suhosin.get.max_value_length</code> a 1024 o superiore in <code>php.ini</code>, ed impostare <code>$wgResourceLoaderMaxQueryLength</code> allo stesso valore in <code>LocalSettings.php</code>.",
+       "config-suhosin-max-value-length": "Suhosin è installato e limita il parametro GET <code>length</code> a $1 byte.\nMediaWiki richiede un valore per <code>suhosin.get.max_value_length</code> di almeno $2. Disattiva questa impostazione, o aumenta questo valore a $3 in <code>php.ini</code>.",
        "config-using-32bit": "<strong>Attenzione</strong> sembra che il tuo sistema utilizzi interi a 32-bit, ciò [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit non è raccomandato].",
        "config-db-type": "Tipo di database:",
        "config-db-host": "Host del database:",
index 5da1914..511b0da 100644 (file)
        "config-uploads-not-safe": "<strong>警告:</strong> アップロードの既定ディレクトリ <code>$1</code> に、任意のスクリプト実行に関する脆弱性があります。\nMediaWiki はアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化する前に、[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security このセキュリティ上の脆弱性を解決する]ことを強く推奨します。",
        "config-no-cli-uploads-check": "<strong>警告:</strong> アップロード用のデフォルトディレクトリ (<code>$1</code>) が、CLIでのインストール中に任意のスクリプト実行の脆弱性チェックを受けていません。",
        "config-brokenlibxml": "このシステムで使われているPHPとlibxml2のバージョンのこの組み合わせにはバグがあります。具体的には、MediaWikiやその他のウェブアプリケーションでhiddenデータが破損する可能性があります。\nlibxml2を2.7.3以降のバージョンにアップグレードしてください([https://bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。\nインストールを終了します。",
-       "config-suhosin-max-value-length": "Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。\nMediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。\n可能な限り、<code>php.ini</code> で <code>suhosin.get.max_value_length</code> を 1024 以上に設定し、同じ値を <code>LocalSettings.php</code> 内で <code>$wgResourceLoaderMaxQueryLength</code> に設定してください。",
+       "config-suhosin-max-value-length": "Suhosin がインストールされており、GET パラメーターの <code>length</code> を $1 バイトに制限しています。(訳注:\nMediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。)\n可能な限り、 <code>suhosin.get.max_value_length</code> を $2 以上に設定します。これを無効に変更するか、<code>php.ini</code> で $3 に増加してください。",
        "config-using-32bit": "<strong>警告:</strong>システムが32ビットで動作しているようです。 これは[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit 非推奨]です。",
        "config-db-type": "データベースの種類:",
        "config-db-host": "データベースのホスト:",
index 7a13bde..21ca17c 100644 (file)
        "config-uploads-not-safe": "<strong>Waarschuwing:</strong> uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.\nHoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
        "config-no-cli-uploads-check": "''Waarschuwing:'' uw standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
        "config-brokenlibxml": "Uw systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.\nUpgrade naar libxml2 2.7.3 of hoger([https://bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).\nDe installatie wordt afgebroken.",
-       "config-suhosin-max-value-length": "Suhosin is geïnstalleerd en beperkt de GET-parameter <code>length</code> tot $1 bytes.\nDe ResourceLoader van MediaWiki omzeilt deze beperking, maar dat is slecht voor de prestaties.\nAls het mogelijk is, moet u de waarde \"<code>suhosin.get.max_value_length</code>\" in <code>php.ini</code> instellen op 1024 of hoger en <code>$wgResourceLoaderMaxQueryLength</code> in LocalSettings.php op dezelfde waarde instellen.",
+       "config-suhosin-max-value-length": "Suhosin is geïnstalleerd en beperkt de GET-parameter <code>length</code> tot $1 bytes.\nMediaWiki vereist dat <code>suhosin.get.max_value_length</code> ten minste $2 is. Schakel deze instelling uit, of verhoog deze in <code>php.ini</code> naar $3.",
        "config-using-32bit": "<strong>Pas op:</strong> uw systeem lijkt met 32-bit integers te werken. Dit is [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit anders dan aangeraden].",
        "config-db-type": "Databasetype:",
        "config-db-host": "Databasehost:",
index fc66bdb..f3f211e 100644 (file)
@@ -24,7 +24,8 @@
                        "Sethakill",
                        "Peter Bowman",
                        "Ankam",
-                       "Railfail536"
+                       "Railfail536",
+                       "Rail"
                ]
        },
        "config-desc": "Instalator MediaWiki",
index 1f8a3c6..6c58c43 100644 (file)
        "config-uploads-not-safe": "Used as a part of environment check result. Parameters:\n* $1 - name of directory for images: <code>$IP/images/</code>",
        "config-no-cli-uploads-check": "CLI = [[w:Command-line interface|command-line interface]] (i.e. the installer runs as a command-line script, not using HTML interface via an internet browser)",
        "config-brokenlibxml": "Status message in the MediaWiki installer environment checks.",
-       "config-suhosin-max-value-length": "{{doc-important|Do not translate \"length\", \"suhosin.get.max_value_length\", and \"php.ini\".}}\nThis error message is shown when PHP configuration <code>suhosin.get.max_value_length</code> is not high enough.\n\n* $1 - The current value\n* $2 - The minimum required value\n* $3 - The recommended value\n",
+       "config-suhosin-max-value-length": "{{doc-important|Do not translate \"length\", \"suhosin.get.max_value_length\", and \"php.ini\".}}\nThis error message is shown when PHP configuration <code>suhosin.get.max_value_length</code> is not high enough.\n\n* $1 - The current value\n* $2 - The minimum required value\n* $3 - The recommended value",
        "config-using-32bit": "Warning message shown when installing on a 32-bit system.",
        "config-db-type": "Field label in the MediaWiki installer followed by possible database types.",
        "config-db-host": "Used as label.\n\nAlso used in {{msg-mw|Config-missing-db-host}}.",
index 3ea2df6..b83cf18 100644 (file)
        "config-mysql-innodb": "InnoDB (preporučeno)",
        "config-site-name": "Ime wikija:",
        "config-site-name-help": "Ovo će se pojaviti u naslovnoj traci pregledača i na raznim drugim mestima.",
+       "config-site-name-blank": "Upišite ime sajta.",
        "config-project-namespace": "Projektni imenski prostor:",
        "config-ns-generic": "Projekat",
        "config-ns-site-name": "Isto ime kao wikija: $1",
        "config-admin-password-blank": "Upišite lozinku za administratorski račun",
        "config-admin-password-mismatch": "Lozinke što ste upisali se ne poklapaju.",
        "config-admin-email": "E-mail adresa:",
+       "config-admin-error-bademail": "Upisali ste neispravnu adresu e-pošte",
+       "config-pingback": "Dijeli podatke o instalaciji s programerima MediaWikija.",
+       "config-almost-done": "Skoro ste gotovi!\nSada možete preskočiti preostala postavljivanja i odmah instalirati wiki.",
+       "config-optional-continue": "Postavi mi više pitanja.",
+       "config-optional-skip": "Već mi je dosadilo, daj samo instaliraj wiki.",
        "config-profile": "Profil korisničkih prava:",
        "config-profile-wiki": "Otvoren wiki",
        "config-profile-no-anon": "Neophodno otvaranje računa",
        "config-profile-private": "Privatan wiki",
        "config-license": "Autorska prava i licenca:",
        "config-license-none": "Bez podnožja za licencu",
+       "config-license-cc-choose": "Odaberite drugu licencu Creative Commonsa na vaš izbor",
        "config-email-settings": "Podešavanja e-pošte",
        "config-enable-email": "Omogući odlaznu e-poštu",
        "config-email-user": "Omogući slanje e-poruka među korisnicima",
        "config-email-usertalk-help": "Omogući korisnicima da primaju obaveštenja o promenama u njihovim korisničkim razgovornim stranicama ako su ih omogućili u podešavanjima.",
        "config-email-watchlist": "Omogući obaveštenja o spisku praćenja",
        "config-email-watchlist-help": "Omogući korisnicima da primaju obaveštenja o svojim nadgledanim stranicama ako su ih omogućili u podešavanjima.",
+       "config-email-auth": "Omogući potvrdu identiteta putem e-pošte",
+       "config-email-sender": "Povratna adresa e-pošte:",
        "config-upload-settings": "Otpremanja slika i datoteka",
        "config-upload-enable": "Omogući postavljanje datoteka",
        "config-upload-deleted": "Folder za obrisane datoteke:",
        "config-memcached-servers": "Memcached-serveri:",
        "config-memcached-help": "Lista IP adresa za uporabu u Memcached.\nTreba da se navede jednu u svaki red, kao i port što će se koristiti. Na primer:\n 127.0.0.1:11211\n 192.168.1.25:1234",
        "config-memcache-needservers": "Odabrali ste Memcached kao vaš tip međuspremnika (keša), ali niste naveli nijedan server.",
+       "config-extensions": "Dodaci",
+       "config-skins": "Skinovi",
+       "config-skins-use-as-default": "Koristi kao predodređenu",
+       "config-skins-missing": "Nisam pronašao nijednu temu. MediaWiki će koristiti rezervnu temu dok ne instalirate druge.",
+       "config-skins-must-enable-some": "Će treba izabrati barem jednu temu.",
+       "config-skins-must-enable-default": "Tema koju ste izabrali za predodređenu mora se omogućiti.",
        "config-install-step-done": "gotovo",
        "config-install-step-failed": "nije uspjelo",
        "config-install-extensions": "Uključujem dodatke",
        "config-help": "pomoć",
        "config-help-tooltip": "kliknite da rasklopite",
        "config-nofile": "Datoteka \"$1\" nije pronađena. Da nije obrisana?",
+       "config-extension-link": "Jeste li znali da vaš wiki podržava [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions dodatke]?\n\nMožete ih pregledati [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category po kategoriji]",
        "config-skins-screenshots": "$1 (ekr. snimci: $2)",
        "config-extensions-requires": "$1 (zahtjeva $2)",
        "config-screenshot": "ekranski snimak",
index b4046a6..33b05b8 100644 (file)
@@ -276,8 +276,8 @@ class RefreshLinksJob extends Job {
                $title = $page->getTitle();
                // Get the latest ID since acquirePageLock() in runForTitle() 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 name.
-               $latest = $title->getLatestRevID( Title::GAID_FOR_UPDATE );
+               // The works around the chicken/egg problem of determining the scope lock key name
+               $latest = $title->getLatestRevID( Title::READ_LATEST );
 
                $triggeringRevisionId = $this->params['triggeringRevisionId'] ?? null;
                if ( $triggeringRevisionId && $triggeringRevisionId !== $latest ) {
index 00f6e99..c433e47 100644 (file)
@@ -3,16 +3,7 @@
 namespace Wikimedia\Message;
 
 /**
- * ITextFormatter is a simplified interface to the Message class. It converts
- * MessageValue message specifiers to localized text in a certain language.
- *
- * MessageValue supports message keys, and parameters with a wide variety of
- * types. It does not expose any details of how messages are retrieved from
- * storage or what format they are stored in.
- *
- * Thus, TextFormatter supports single message keys, but not the concept of
- * presence or absence of a key from storage. So it does not support
- * fallback sequences of multiple keys.
+ * Converts MessageValue message specifiers to localized plain text in a certain language.
  *
  * The caller cannot modify the details of message translation, such as which
  * of multiple sources the message is taken from. Any such flags may be injected
index c6a9c65..970ef6b 100644 (file)
@@ -3,19 +3,17 @@
 namespace Wikimedia\Message;
 
 /**
- * The class for list parameters
+ * Value object representing a message parameter that consists of a list of values.
+ *
+ * Message parameter classes are pure value objects and are safely newable.
  */
 class ListParam extends MessageParam {
        private $listType;
 
        /**
-        * @param string $listType One of the ListType constants:
-        *   - ListType::COMMA: A comma-separated list
-        *   - ListType::SEMICOLON: A semicolon-separated list
-        *   - ListType::PIPE: A pipe-separated list
-        *   - ListType::TEXT: A natural language list, separated by commas and
-        *     the word "and".
-        * @param (MessageParam|string)[] $elements An array of parameters
+        * @param string $listType One of the ListType constants.
+        * @param (MessageParam|MessageValue|string|int|float)[] $elements Values in the list.
+        *  Values that are not instances of MessageParam are wrapped using ParamType::TEXT.
         */
        public function __construct( $listType, array $elements ) {
                $this->type = ParamType::LIST;
@@ -24,8 +22,8 @@ class ListParam extends MessageParam {
                foreach ( $elements as $element ) {
                        if ( $element instanceof MessageParam ) {
                                $this->value[] = $element;
-                       } elseif ( is_scalar( $element ) ) {
-                               $this->value[] = new TextParam( ParamType::TEXT, $element );
+                       } elseif ( is_scalar( $element ) || $element instanceof MessageValue ) {
+                               $this->value[] = new ScalarParam( ParamType::TEXT, $element );
                        } else {
                                throw new \InvalidArgumentException(
                                        'ListParam elements must be MessageParam or scalar' );
index 60f3a82..f846464 100644 (file)
@@ -4,8 +4,7 @@ namespace Wikimedia\Message;
 
 /**
  * The constants used to specify list types. The values of the constants are an
- * unstable implementation detail and correspond to the names of the list types
- * in the Message class.
+ * unstable implementation detail.
  */
 class ListType {
        /** A comma-separated list */
index 8162212..b6475a7 100644 (file)
@@ -3,7 +3,9 @@
 namespace Wikimedia\Message;
 
 /**
- * The base class for message parameters.
+ * Value object representing a message parameter that consists of a list of values.
+ *
+ * Message parameter classes are pure value objects and are safely newable.
  */
 abstract class MessageParam {
        protected $type;
@@ -21,7 +23,7 @@ abstract class MessageParam {
        /**
         * Get the input value of the parameter
         *
-        * @return int|float|string|array
+        * @return mixed
         */
        public function getValue() {
                return $this->value;
index 13b97f2..1d80d60 100644 (file)
@@ -3,7 +3,13 @@
 namespace Wikimedia\Message;
 
 /**
- * A MessageValue holds a key and an array of parameters
+ * Value object representing a message for i18n.
+ *
+ * A MessageValue holds a key and an array of parameters. It can be converted
+ * to a string in a particular language using formatters obtained from an
+ * IMessageFormatterFactory.
+ *
+ * MessageValues are pure value objects and are safely newable.
  */
 class MessageValue {
        /** @var string */
@@ -14,9 +20,8 @@ class MessageValue {
 
        /**
         * @param string $key
-        * @param array $params Each element of the parameter array
-        *   may be either a MessageParam or a scalar. If it is a scalar, it is
-        *   converted to a parameter of type TEXT.
+        * @param (MessageParam|MessageValue|string|int|float)[] $params Values that are not instances
+        *  of MessageParam are wrapped using ParamType::TEXT.
         */
        public function __construct( $key, $params = [] ) {
                $this->key = $key;
@@ -45,7 +50,7 @@ class MessageValue {
        /**
         * Chainable mutator which adds text parameters and MessageParam parameters
         *
-        * @param mixed ...$values Scalar or MessageParam values
+        * @param MessageParam|MessageValue|string|int|float ...$values
         * @return MessageValue
         */
        public function params( ...$values ) {
@@ -53,7 +58,7 @@ class MessageValue {
                        if ( $value instanceof MessageParam ) {
                                $this->params[] = $value;
                        } else {
-                               $this->params[] = new TextParam( ParamType::TEXT, $value );
+                               $this->params[] = new ScalarParam( ParamType::TEXT, $value );
                        }
                }
                return $this;
@@ -63,12 +68,12 @@ class MessageValue {
         * Chainable mutator which adds text parameters with a common type
         *
         * @param string $type One of the ParamType constants
-        * @param mixed ...$values Scalar values
+        * @param MessageValue|string|int|float ...$values Scalar values
         * @return MessageValue
         */
        public function textParamsOfType( $type, ...$values ) {
                foreach ( $values as $value ) {
-                       $this->params[] = new TextParam( $type, $value );
+                       $this->params[] = new ScalarParam( $type, $value );
                }
                return $this;
        }
@@ -77,7 +82,8 @@ class MessageValue {
         * Chainable mutator which adds list parameters with a common type
         *
         * @param string $listType One of the ListType constants
-        * @param array ...$values Each value should be an array of list items.
+        * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+        *  is an array of items suitable to pass as $params to ListParam::__construct()
         * @return MessageValue
         */
        public function listParamsOfType( $listType, ...$values ) {
@@ -88,9 +94,9 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds parameters of type text.
+        * Chainable mutator which adds parameters of type text (ParamType::TEXT).
         *
-        * @param string ...$values
+        * @param MessageValue|string|int|float ...$values
         * @return MessageValue
         */
        public function textParams( ...$values ) {
@@ -98,9 +104,9 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds numeric parameters
+        * Chainable mutator which adds numeric parameters (ParamType::NUM).
         *
-        * @param mixed ...$values
+        * @param int|float ...$values
         * @return MessageValue
         */
        public function numParams( ...$values ) {
@@ -109,8 +115,10 @@ class MessageValue {
 
        /**
         * Chainable mutator which adds parameters which are a duration specified
-        * in seconds. This is similar to timePeriodParams() except that the result
-        * will be more verbose.
+        * in seconds (ParamType::DURATION_LONG).
+        *
+        * This is similar to shorDurationParams() except that the result will be
+        * more verbose.
         *
         * @param int|float ...$values
         * @return MessageValue
@@ -120,8 +128,10 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds parameters which are a time period in seconds.
-        * This is similar to durationParams() except that the result will be more
+        * Chainable mutator which adds parameters which are a duration specified
+        * in seconds (ParamType::DURATION_SHORT).
+        *
+        * This is similar to longDurationParams() except that the result will be more
         * compact.
         *
         * @param int|float ...$values
@@ -132,10 +142,10 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds parameters which are an expiry timestamp
-        * as used in the MediaWiki database schema.
+        * Chainable mutator which adds parameters which are an expiry timestamp (ParamType::EXPIRY).
         *
-        * @param string ...$values
+        * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library,
+        *  or "infinity"
         * @return MessageValue
         */
        public function expiryParams( ...$values ) {
@@ -143,7 +153,7 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds parameters which are a number of bytes.
+        * Chainable mutator which adds parameters which are a number of bytes (ParamType::SIZE).
         *
         * @param int ...$values
         * @return MessageValue
@@ -154,7 +164,7 @@ class MessageValue {
 
        /**
         * Chainable mutator which adds parameters which are a number of bits per
-        * second.
+        * second (ParamType::BITRATE).
         *
         * @param int|float ...$values
         * @return MessageValue
@@ -164,9 +174,13 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds parameters of type "raw".
+        * Chainable mutator which adds "raw" parameters (ParamType::RAW).
         *
-        * @param mixed ...$values
+        * Raw parameters are substituted after formatter processing. The caller is responsible
+        * for ensuring that the value will be safe for the intended output format, and
+        * documenting what that intended output format is.
+        *
+        * @param string ...$values
         * @return MessageValue
         */
        public function rawParams( ...$values ) {
@@ -174,21 +188,27 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds parameters of type "plaintext".
+        * Chainable mutator which adds plaintext parameters (ParamType::PLAINTEXT).
+        *
+        * Plaintext parameters are substituted after formatter processing. The value
+        * will be escaped by the formatter as appropriate for the target output format
+        * so as to be represented as plain text rather than as any sort of markup.
+        *
+        * @param string ...$values
+        * @return MessageValue
         */
        public function plaintextParams( ...$values ) {
                return $this->textParamsOfType( ParamType::PLAINTEXT, ...$values );
        }
 
        /**
-        * Chainable mutator which adds comma lists. Each comma list is an array of
-        * list elements, and each list element is either a MessageParam or a
-        * string. String parameters are converted to parameters of type "text".
+        * Chainable mutator which adds comma lists (ListType::COMMA).
         *
         * The list parameters thus created are formatted as a comma-separated list,
         * or some local equivalent.
         *
-        * @param (MessageParam|string)[] ...$values
+        * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+        *  is an array of items suitable to pass as $params to ListParam::__construct()
         * @return MessageValue
         */
        public function commaListParams( ...$values ) {
@@ -196,15 +216,13 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds semicolon lists. Each semicolon list is an
-        * array of list elements, and each list element is either a MessageParam
-        * or a string. String parameters are converted to parameters of type
-        * "text".
+        * Chainable mutator which adds semicolon lists (ListType::SEMICOLON).
         *
         * The list parameters thus created are formatted as a semicolon-separated
         * list, or some local equivalent.
         *
-        * @param (MessageParam|string)[] ...$values
+        * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+        *  is an array of items suitable to pass as $params to ListParam::__construct()
         * @return MessageValue
         */
        public function semicolonListParams( ...$values ) {
@@ -212,14 +230,13 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds pipe lists. Each pipe list is an array of
-        * list elements, and each list element is either a MessageParam or a
-        * string. String parameters are converted to parameters of type "text".
+        * Chainable mutator which adds pipe lists (ListType::PIPE).
         *
         * The list parameters thus created are formatted as a pipe ("|") -separated
         * list, or some local equivalent.
         *
-        * @param (MessageParam|string)[] ...$values
+        * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value
+        *  is an array of items suitable to pass as $params to ListParam::__construct()
         * @return MessageValue
         */
        public function pipeListParams( ...$values ) {
@@ -227,9 +244,7 @@ class MessageValue {
        }
 
        /**
-        * Chainable mutator which adds text lists. Each text list is an array of
-        * list elements, and each list element is either a MessageParam or a
-        * string. String parameters are converted to parameters of type "text".
+        * Chainable mutator which adds natural-language lists (ListType::AND).
         *
         * The list parameters thus created, when formatted, are joined as in natural
         * language. In English, this means a comma-separated list, with the last
index 890ef38..4db7112 100644 (file)
@@ -4,44 +4,62 @@ namespace Wikimedia\Message;
 
 /**
  * The constants used to specify parameter types. The values of the constants
- * are an unstable implementation detail, and correspond to the names of the
- * parameter types in the Message class.
+ * are an unstable implementation detail.
+ *
+ * Unless otherwise noted, these should be used with an instance of ScalarParam.
  */
 class ParamType {
-       /** A simple text parameter */
+       /** A simple text string or another MessageValue, not otherwise formatted. */
        const TEXT = 'text';
 
        /** A number, to be formatted using local digits and separators */
        const NUM = 'num';
 
-       /** A number of seconds, to be formatted as natural language text. */
+       /**
+        * A number of seconds, to be formatted as natural language text.
+        * The value will be output exactly.
+        */
        const DURATION_LONG = 'duration';
 
-       /** A number of seconds, to be formatted in an abbreviated way. */
+       /**
+        * A number of seconds, to be formatted as natural language text in an abbreviated way.
+        * The output will be rounded to an appropriate magnitude.
+        */
        const DURATION_SHORT = 'timeperiod';
 
        /**
-        * An expiry time for a block. The input is either a timestamp in one
-        * of the formats accepted by the Wikimedia\Timestamp library, or
-        * "infinity" for an infinite block.
+        * An expiry time.
+        *
+        * The input is either a timestamp in one of the formats accepted by the
+        * Wikimedia\Timestamp library, or "infinity" if the thing doesn't expire.
+        *
+        * The output is a date and time in local format, or a string representing
+        * an "infinite" expiry.
         */
        const EXPIRY = 'expiry';
 
-       /** A number of bytes. */
+       /** A number of bytes. The output will be rounded to an appropriate magnitude. */
        const SIZE = 'size';
 
-       /** A number of bits per second. */
+       /** A number of bits per second. The output will be rounded to an appropriate magnitude. */
        const BITRATE = 'bitrate';
 
-       /** The list type (ListParam) */
+       /** A list of values. Must be used with ListParam. */
        const LIST = 'list';
 
        /**
-        * A text parameter which is substituted after preprocessing, and so is
-        * not available to the preprocessor and cannot be modified by it.
+        * A text parameter which is substituted after formatter processing.
+        *
+        * The creator of the parameter and message is responsible for ensuring
+        * that the value will be safe for the intended output format, and
+        * documenting what that intended output format is.
         */
        const RAW = 'raw';
 
-       /** Reserved for future use. */
+       /**
+        * A text parameter which is substituted after formatter processing.
+        * The output will be escaped as appropriate for the output format so
+        * as to represent plain text rather than any sort of markup.
+        */
        const PLAINTEXT = 'plaintext';
 }
diff --git a/includes/libs/Message/README.md b/includes/libs/Message/README.md
new file mode 100644 (file)
index 0000000..9f6255a
--- /dev/null
@@ -0,0 +1,291 @@
+Wikimedia Internationalization Library
+======================================
+
+This library provides interfaces and value objects for internationalization (i18n)
+of applications in PHP.
+
+It is based on the i18n code used in MediaWiki, and is also intended to be
+compatible with [jQuery.i18n], a JavaScript i18n library.
+
+Concepts
+--------
+
+Any text string that is needed in an application is a **message**. This might
+be something like a button label, a sentence, or a longer text. Each message is
+assigned a **message key**, which is used as the identifier in code.
+
+Each message is translated into various languages, each represented by a
+**language code**. The message's text (as translated into each language) can
+contain **placeholders**, which represents a place in the message where a
+**parameter** is to be inserted, and **formatting commands**. It might be plain
+text other than these placeholders and formatting commands, or it might be in a
+**markup language** such as wikitext or Markdown.
+
+A **formatter** is used to convert the message key and parameters into a text
+representation in a particular language and **output format**.
+
+The library itself imposes few restrictions on all of these concepts; this
+document contains recommendations to help various implementations operate in
+compatible ways.
+
+Usage
+-----
+
+<pre lang="php">
+use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\MessageParam;
+use Wikimedia\Message\ParamType;
+
+// Constructor interface
+$message = new MessageValue( 'message-key', [
+    'parameter',
+    new MessageValue( 'another-message' ),
+    new MessageParam( ParamType::NUM, 12345 ),
+] );
+
+// Fluent interface
+$message = ( new MessageValue( 'message-key' ) )
+    ->params( 'parameter', new MessageValue( 'another-message' ) )
+    ->numParams( 12345 );
+
+// Formatting
+$messageFormatter = $serviceContainter->get( 'MessageFormatterFactory' )->getTextFormatter( 'de' );
+$output = $messageFormatter->format( $message );
+</pre>
+
+Class Overview
+--------------
+
+### Messages
+
+Messages and their parameters are represented by newable value objects.
+
+**MessageValue** represents an instance of a message, holding the key and any
+parameters. It is mutable in that parameters can be added to the object after
+creation.
+
+**MessageParam** is an abstract value class representing a parameter to a message.
+It has a type (using constants defined in the **ParamType** class) and a value. It
+has two implementations:
+
+- **ScalarParam** represents a single-valued parameter, such as a text string, a
+  number, or another message.
+- **ListParam** represents a list of values, which will be joined together with
+  appropriate separators. It has a "list type" (using constants defined in the
+  **ListType** class) defining the desired separators.
+
+### Formatters
+
+A formatter for a particular language is obtained from an implementation of
+**IMessageFormatterFactory**. No implementation of this interface is provided by
+this library. If an environment needs its formatters to vary behavior on things
+other than the language code, for example selecting among multiple sources of
+messages or markup language used for processing message texts, it should define
+a MessageFormatterFactoryFactory of some sort to provide appropriate
+IMessageFormatterFactory implementations.
+
+There is no one base interface for all formatters; the intent is that type
+hinting will ensure that the formatter being used will produce output in the
+expected output format. The defined output formats are:
+
+- **ITextFormatter** produces plain text output.
+
+No implementation of these interfaces are provided by this library.
+
+Formatter implementations are expected to perform the following procedure to
+generate the output string:
+
+1. Fetch the message's translation in the formatter's language. Details of this
+   fetching are unspecified here.
+   - If no translation is found in the formatter's language, it should attempt
+     to fall back to appropriate other languages. Details of the fallback are
+     unspecified here.
+   - If no translation can be found in any fallback language, a string should
+        be returned that indicates at minimum the message key that was unable to
+        be found.
+2. Replace placeholders with parameter values.
+   - Note that placeholders must not be replaced recursively. That is, if a
+     parameter's value contains text that looks like a placeholder, it must not
+     be replaced as if it really were a placeholder.
+   - Certain types of parameters are not substituted directly at this stage.
+     Instead their placeholders must be replaced with an opaque representation
+     that will not be misinterpreted during later stages.
+     - Parameters of type RAW or PLAINTEXT
+     - TEXT parameters with a MessageValue as the value
+     - LIST parameters with any late-substituted value as one of their values.
+3. Process any formatting commands.
+4. Process the source markup language to produce a string in the desired output
+   format. This may be a no-op, and may be combined with the previous step if
+   the markup language implements compatible formatting commands.
+5. Replace any opaque representations from step 2 with the actual values of
+   the corresponding parameters.
+
+Guidelines for Interoperability
+-------------------------------
+
+Besides allowing for libraries to safely supply their own translations for
+every app using them, and apps to easily use libraries' translations instead of
+having to retranslate everything, following these guidelines will also help
+open source projects use [translatewiki.net] for crowdsourced volunteer
+translation into many languages.
+
+### Language codes
+
+[BCP 47] language tags should be used for language codes. If a supplied
+language tag is not recognized, at minimum the corresponding tag with all
+optional subtags stripped should be tried as a fallback.
+
+All messages must have a translation in English (code "en"). All languages
+should fall back to English as a last resort.
+
+The English translations should use `{{PLURAL:...}}` and `{{GENDER:...}}` even
+when English doesn't make a grammatical distinction, to signal to translators
+that plural/gender support is available.
+
+Language code "qqq" is reserved for documenting messages. Documentation should
+describe the context in which the message is used and the values of all
+parameters used with the message. Generally this is written in English.
+Attempting to obtain a message formatter for "qqq" should return one for "en"
+instead.
+
+Language code "qqx" is reserved for debugging. Rather than retrieving
+translations from some underlying storage, every key should act as if it were
+translated as something `(key-name: $1, $2, $3)` with the number of
+placeholders depending on how many parameters are included in the
+MessageValue.
+
+### Message keys
+
+Message keys intended for use with external implementations should follow
+certain guidelines for interoperability:
+
+- Keys should be restricted to the regular expression `/^[a-z][a-z0-9-]*$/`.
+  That is, it should consist of lowercase ASCII letters, numbers, and hyphen
+  only, and should begin with a letter.
+- Keys should be prefixed to help avoid collisions. For example, a library
+  named "ApplePicker" should prefix its message keys with "applepicker-".
+- Common values needing translation, such as names of months and weekdays,
+  should not be prefixed by each library. Libraries needing these should use
+  keys from the [Common Locale Data Repository][CLDR] and document this
+  requirement, and environments should provide these messages.
+
+### Message format
+
+Placeholders are represented by `$1`, `$2`, `$3`, and so on. Text like `$100`
+is interpreted as a placeholder for parameter 100 if 100 or more parameters
+were supplied, as a placeholder for parameter 10 followed by text "0" if
+between ten and 99 parameters were supplied, and as a placeholder for parameter
+1 followed by text "00" if between one and nine parameters were supplied.
+
+All formatting commands look like `{{NAME:$value1|$value2|$value3|...}}`. Braces
+are to be balanced, e.g. `{{NAME:foo|{{bar|baz}}}}` has $value1 as "foo" and
+$value2 as "{{bar|baz}}". The name is always case-insensitive.
+
+Anything syntactically resembling a placeholder or formatting command that does
+not correspond to an actual paramter or known command should be left unchanged
+for processing by the markup language processor.
+
+Libraries providing messages for use by externally-defined formatters should
+generally assume no markup language will be applied, and should avoid
+constructs used by common markup languages unless they also make sense when
+read as plain text.
+
+### Formatting commands
+
+The following formatting commands should be supported.
+
+#### PLURAL
+
+`{{PLURAL:$count|$formA|$formB|...}}` is used to produce plurals.
+
+$count is a number, which may have been formatted with ParamType::NUM.
+
+The number of forms and which count corresponds to which form depend on the
+language, for example English uses `{{PLURAL:$1|one|other}}` while Arabic uses
+`{{PLURAL:$1|zero|one|two|few|many|other}}`. Details are defined in
+[CLDR][CLDR plurals].
+
+It is not possible to "skip" positions while still suppling later ones. If too
+few values are supplied, the final form is repeated for subsequent positions.
+
+If there is an explicit plural form to be given for a specific number, it may
+be specified with syntax like `{{PLURAL:$1|one egg|$1 eggs|12=a dozen eggs}}`.
+
+#### GENDER
+
+`{{GENDER:$name|$masculine|$feminine|$unspecified}}` is used to handle
+grammatical gender, typically when messages refer to user accounts.
+
+This supports three grammatical genders: "male", "female", and a third option
+for cases where the gender is unspecified, unknown, or neither male nor female.
+It does not attempt to handle animate-inanimate or [T-V] distinctions.
+
+$name is a user account name or other similar identifier. If the name given
+does not correspond to any known user account, it should probably use the
+$unspecified gender.
+
+If $feminine and/or $unspecified is not specified, the value of $masculine
+is normally used in its place.
+
+#### GRAMMAR
+
+`{{GRAMMAR:$form|$term}}` converts a term to an appropriate grammatical form.
+
+If no mapping for $term to $form exists, $term should be returned unchanged.
+
+See [jQuery.i18n § Grammar][jQuery.i18n grammar] for details.
+
+#### BIDI
+
+`{{BIDI:$text}}` applies directional isolation to the wrapped text, to attempt
+to avoid errors where directionally-neutral characters are wrongly displayed
+when between LTR and RTL content.
+
+This should output U+202A (left-to-right embedding) or U+202B (right-to-left
+embedding) before the text, depending on the directionality of the first
+strongly-directional character in $text, and U+202C (pop directional
+formatting) after, or do something equivalent for the target output format.
+
+### Supplying translations
+
+Code intending its messages to be used by externally-defined formatters should
+supply the translations as described by
+[jQuery.i18n § Message File Format][jQuery.i18n file format].
+
+In brief, the base directory of the library should contain a directory named
+"i18n". This directory should contain JSON files named by code such as
+"en.json", "de.json", "qqq.json", each with contents like:
+
+```json
+{
+    "@metadata": {
+        "authors": [
+            "Alice",
+            "Bob",
+            "Carol",
+            "David"
+        ],
+        "last-updated": "2012-09-21"
+    },
+    "appname-title": "Example Application",
+    "appname-sub-title": "An example application",
+    "appname-header-introduction": "Introduction",
+    "appname-about": "About this application",
+    "appname-footer": "Footer text"
+}
+```
+
+Formatter implementations should be able to consume message data supplied in
+this format, either directly via registration of i18n directories to check or
+by providing tooling to incorporate it during a build step.
+
+
+---
+[jQuery.i18n]: https://github.com/wikimedia/jquery.i18n
+[BCP 47]: https://tools.ietf.org/rfc/bcp/bcp47.txt
+[CLDR]: http://cldr.unicode.org/
+[CLDR plurals]: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+[jQuery.i18n grammar]: https://github.com/wikimedia/jquery.i18n#grammar
+[jQuery.i18n file format]: https://github.com/wikimedia/jquery.i18n#message-file-format
+[translatewiki.net]: https://translatewiki.net/wiki/Translating:New_project
+[T-V]: https://en.wikipedia.org/wiki/T%E2%80%93V_distinction
diff --git a/includes/libs/Message/ScalarParam.php b/includes/libs/Message/ScalarParam.php
new file mode 100644 (file)
index 0000000..c17bc7f
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Wikimedia\Message;
+
+/**
+ * Value object representing a message parameter holding a single value.
+ *
+ * Message parameter classes are pure value objects and are safely newable.
+ */
+class ScalarParam extends MessageParam {
+       /**
+        * Construct a text parameter
+        *
+        * @param string $type One of the ParamType constants.
+        * @param string|int|float|MessageValue $value
+        */
+       public function __construct( $type, $value ) {
+               $this->type = $type;
+               $this->value = $value;
+       }
+
+       public function dump() {
+               if ( $this->value instanceof MessageValue ) {
+                       $contents = $this->value->dump();
+               } else {
+                       $contents = htmlspecialchars( $this->value );
+               }
+               return "<{$this->type}>" . $contents . "</{$this->type}>";
+       }
+}
diff --git a/includes/libs/Message/TextParam.php b/includes/libs/Message/TextParam.php
deleted file mode 100644 (file)
index c1a1f08..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-namespace Wikimedia\Message;
-
-class TextParam extends MessageParam {
-       /**
-        * Construct a text parameter
-        *
-        * @param string $type May be one of:
-        *   - ParamType::TEXT: A simple text parameter
-        *   - ParamType::NUM: A number, to be formatted using local digits and
-        *     separators
-        *   - ParamType::DURATION_LONG: A number of seconds, to be formatted as natural
-        *     language text.
-        *   - ParamType::DURATION_SHORT: A number of seconds, to be formatted in an
-        *     abbreviated way.
-        *   - ParamType::EXPIRY: An expiry time for a block. The input is either
-        *     a timestamp in one of the formats accepted by the Wikimedia\Timestamp
-        *     library, or "infinity" for an infinite block.
-        *   - ParamType::SIZE: A number of bytes.
-        *   - ParamType::BITRATE: A number of bits per second.
-        *   - ParamType::RAW: A text parameter which is substituted after
-        *     preprocessing, and so is not available to the preprocessor and cannot
-        *     be modified by it.
-        *   - ParamType::PLAINTEXT: Reserved for future use.
-        *
-        * @param string|int|float $value
-        */
-       public function __construct( $type, $value ) {
-               $this->type = $type;
-               $this->value = $value;
-       }
-
-       public function dump() {
-               return "<{$this->type}>" . htmlspecialchars( $this->value ) . "</{$this->type}>";
-       }
-}
index 71a0e34..4b381f8 100644 (file)
@@ -93,7 +93,7 @@ class StatusValue {
         *     1 => object(StatusValue) # The StatusValue with warning messages, only
         * ]
         *
-        * @return StatusValue[]
+        * @return static[]
         */
        public function splitByErrorType() {
                $errorsOnlyStatusValue = clone $this;
index c333a5e..9ed7ae3 100644 (file)
@@ -40,6 +40,7 @@
  * @file
  * @ingroup FileBackend
  */
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
@@ -63,23 +64,22 @@ class FSFileBackend extends FileBackendStore {
        protected $basePath;
 
        /** @var array Map of container names to root paths for custom container paths */
-       protected $containerPaths = [];
+       protected $containerPaths;
 
+       /** @var int Directory permission mode */
+       protected $dirMode;
        /** @var int File permission mode */
        protected $fileMode;
-       /** @var int File permission mode */
-       protected $dirMode;
-
        /** @var string Required OS username to own files */
        protected $fileOwner;
 
-       /** @var bool */
+       /** @var bool Whether the OS is Windows (otherwise assumed Unix-like)*/
        protected $isWindows;
        /** @var string OS username running this script */
        protected $currentUser;
 
-       /** @var array */
-       protected $hadWarningErrors = [];
+       /** @var bool[] Map of (stack index => whether a warning happened) */
+       private $warningTrapStack = [];
 
        /**
         * @see FileBackendStore::__construct()
@@ -102,11 +102,9 @@ class FSFileBackend extends FileBackendStore {
                        $this->basePath = null; // none; containers must have explicit paths
                }
 
-               if ( isset( $config['containerPaths'] ) ) {
-                       $this->containerPaths = (array)$config['containerPaths'];
-                       foreach ( $this->containerPaths as &$path ) {
-                               $path = rtrim( $path, '/' ); // remove trailing slash
-                       }
+               $this->containerPaths = [];
+               foreach ( ( $config['containerPaths'] ?? [] ) as $container => $path ) {
+                       $this->containerPaths[$container] = rtrim( $path, '/' ); // remove trailing slash
                }
 
                $this->fileMode = $config['fileMode'] ?? 0644;
@@ -124,7 +122,7 @@ class FSFileBackend extends FileBackendStore {
                        // See https://www.php.net/manual/en/migration71.windows-support.php
                        return 0;
                } else {
-                       return FileBackend::ATTR_UNICODE_PATHS;
+                       return self::ATTR_UNICODE_PATHS;
                }
        }
 
@@ -228,20 +226,12 @@ class FSFileBackend extends FileBackendStore {
                }
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $tempFile = $this->tmpFileFactory->newTempFSFile( 'create_', 'tmp' );
+                       $tempFile = $this->stageContentAsTempFile( $params );
                        if ( !$tempFile ) {
                                $status->fatal( 'backend-fail-create', $params['dst'] );
 
                                return $status;
                        }
-                       $this->trapWarnings();
-                       $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
-                       $this->untrapWarnings();
-                       if ( $bytes === false ) {
-                               $status->fatal( 'backend-fail-create', $params['dst'] );
-
-                               return $status;
-                       }
                        $cmd = implode( ' ', [
                                $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
                                escapeshellarg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
@@ -376,34 +366,38 @@ class FSFileBackend extends FileBackendStore {
        protected function doMoveInternal( array $params ) {
                $status = $this->newStatus();
 
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
+               $fsSrcPath = $this->resolveToFSPath( $params['src'] );
+               if ( $fsSrcPath === null ) {
                        $status->fatal( 'backend-fail-invalidpath', $params['src'] );
 
                        return $status;
                }
 
-               $dest = $this->resolveToFSPath( $params['dst'] );
-               if ( $dest === null ) {
+               $fsDstPath = $this->resolveToFSPath( $params['dst'] );
+               if ( $fsDstPath === null ) {
                        $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
 
                        return $status;
                }
 
-               if ( !is_file( $source ) ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-move', $params['src'] );
-                       }
-
-                       return $status; // do nothing; either OK or bad status
+               if ( $fsSrcPath === $fsDstPath ) {
+                       return $status; // no-op
                }
 
+               $ignoreMissing = !empty( $params['ignoreMissingSource'] );
+
                if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', [
-                               $this->isWindows ? 'MOVE /Y' : 'mv', // (overwrite)
-                               escapeshellarg( $this->cleanPathSlashes( $source ) ),
-                               escapeshellarg( $this->cleanPathSlashes( $dest ) )
-                       ] );
+                       // https://manpages.debian.org/buster/coreutils/mv.1.en.html
+                       // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/move
+                       $encSrc = escapeshellarg( $this->cleanPathSlashes( $fsSrcPath ) );
+                       $encDst = escapeshellarg( $this->cleanPathSlashes( $fsDstPath ) );
+                       if ( $this->isWindows ) {
+                               $writeCmd = "MOVE /Y $encSrc $encDst";
+                               $cmd = $ignoreMissing ? "IF EXIST $encSrc $writeCmd" : $writeCmd;
+                       } else {
+                               $writeCmd = "mv -f $encSrc $encDst";
+                               $cmd = $ignoreMissing ? "test -f $encSrc && $writeCmd" : $writeCmd;
+                       }
                        $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
@@ -412,11 +406,13 @@ class FSFileBackend extends FileBackendStore {
                        };
                        $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
                } else { // immediate write
-                       $this->trapWarnings();
-                       $ok = ( $source === $dest ) ? true : rename( $source, $dest );
-                       $this->untrapWarnings();
-                       clearstatcache(); // file no longer at source
-                       if ( !$ok ) {
+                       // Use rename() here since (a) this clears xattrs, (b) any threads still reading the
+                       // old inode are unaffected since it writes to a new inode, and (c) this is fast and
+                       // atomic within a file system volume (as is normally the case)
+                       $this->trapWarnings( '/: No such file or directory$/' );
+                       $moved = rename( $fsSrcPath, $fsDstPath );
+                       $hadError = $this->untrapWarnings();
+                       if ( $hadError || ( !$moved && !$ignoreMissing ) ) {
                                $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
 
                                return $status;
@@ -429,26 +425,25 @@ class FSFileBackend extends FileBackendStore {
        protected function doDeleteInternal( array $params ) {
                $status = $this->newStatus();
 
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
+               $fsSrcPath = $this->resolveToFSPath( $params['src'] );
+               if ( $fsSrcPath === null ) {
                        $status->fatal( 'backend-fail-invalidpath', $params['src'] );
 
                        return $status;
                }
 
-               if ( !is_file( $source ) ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-delete', $params['src'] );
-                       }
-
-                       return $status; // do nothing; either OK or bad status
-               }
+               $ignoreMissing = !empty( $params['ignoreMissingSource'] );
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', [
-                               $this->isWindows ? 'DEL' : 'unlink',
-                               escapeshellarg( $this->cleanPathSlashes( $source ) )
-                       ] );
+                       // https://manpages.debian.org/buster/coreutils/rm.1.en.html
+                       // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/del
+                       $encSrc = escapeshellarg( $this->cleanPathSlashes( $fsSrcPath ) );
+                       if ( $this->isWindows ) {
+                               $writeCmd = "DEL /Q $encSrc";
+                               $cmd = $ignoreMissing ? "IF EXIST $encSrc $writeCmd" : $writeCmd;
+                       } else {
+                               $cmd = $ignoreMissing ? "rm -f $encSrc" : "rm $encSrc";
+                       }
                        $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-delete', $params['src'] );
@@ -457,10 +452,10 @@ class FSFileBackend extends FileBackendStore {
                        };
                        $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
                } else { // immediate write
-                       $this->trapWarnings();
-                       $ok = unlink( $source );
-                       $this->untrapWarnings();
-                       if ( !$ok ) {
+                       $this->trapWarnings( '/: No such file or directory$/' );
+                       $deleted = unlink( $fsSrcPath );
+                       $hadError = $this->untrapWarnings();
+                       if ( $hadError || ( !$deleted && !$ignoreMissing ) ) {
                                $status->fatal( 'backend-fail-delete', $params['src'] );
 
                                return $status;
@@ -483,7 +478,7 @@ class FSFileBackend extends FileBackendStore {
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
                $existed = is_dir( $dir ); // already there?
                // Create the directory and its parents as needed...
-               $this->trapWarnings();
+               AtEase::suppressWarnings();
                if ( !$existed && !mkdir( $dir, $this->dirMode, true ) && !is_dir( $dir ) ) {
                        $this->logger->error( __METHOD__ . ": cannot create directory $dir" );
                        $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
@@ -494,7 +489,7 @@ class FSFileBackend extends FileBackendStore {
                        $this->logger->error( __METHOD__ . ": directory $dir is not readable" );
                        $status->fatal( 'directorynotreadableerror', $params['dir'] );
                }
-               $this->untrapWarnings();
+               AtEase::restoreWarnings();
                // Respect any 'noAccess' or 'noListing' flags...
                if ( is_dir( $dir ) && !$existed ) {
                        $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
@@ -519,9 +514,9 @@ class FSFileBackend extends FileBackendStore {
                }
                // Add a .htaccess file to the root of the container...
                if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
-                       $this->trapWarnings();
+                       AtEase::suppressWarnings();
                        $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
-                       $this->untrapWarnings();
+                       AtEase::restoreWarnings();
                        if ( $bytes === false ) {
                                $storeDir = "mwstore://{$this->name}/{$shortCont}";
                                $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
@@ -539,21 +534,17 @@ class FSFileBackend extends FileBackendStore {
                // Unseed new directories with a blank index.html, to allow crawling...
                if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
                        $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
-                       $this->trapWarnings();
-                       if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
+                       if ( $exists && !$this->unlink( "{$dir}/index.html" ) ) { // reverse secure()
                                $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
                        }
-                       $this->untrapWarnings();
                }
                // Remove the .htaccess file from the root of the container...
                if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
                        $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
-                       $this->trapWarnings();
-                       if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
+                       if ( $exists && !$this->unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
                                $storeDir = "mwstore://{$this->name}/{$shortCont}";
                                $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
                        }
-                       $this->untrapWarnings();
                }
 
                return $status;
@@ -564,11 +555,9 @@ class FSFileBackend extends FileBackendStore {
                list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               $this->trapWarnings();
-               if ( is_dir( $dir ) ) {
-                       rmdir( $dir ); // remove directory if empty
-               }
-               $this->untrapWarnings();
+               AtEase::suppressWarnings();
+               rmdir( $dir ); // remove directory if empty
+               AtEase::restoreWarnings();
 
                return $status;
        }
@@ -724,7 +713,7 @@ class FSFileBackend extends FileBackendStore {
 
                        $tmpPath = $tmpFile->getPath();
                        // Copy the source file over the temp file
-                       $this->trapWarnings();
+                       $this->trapWarnings(); // don't trust 'false' if there were errors
                        $isFile = is_file( $source ); // regular files only
                        $copySuccess = $isFile ? copy( $source, $tmpPath ) : false;
                        $hadError = $this->untrapWarnings();
@@ -755,8 +744,19 @@ class FSFileBackend extends FileBackendStore {
                $statuses = [];
 
                $pipes = [];
+               $octalPermissions = '0' . decoct( $this->fileMode );
                foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
+                       $cmd = "{$fileOpHandle->cmd} 2>&1";
+                       // Add a post-operation chmod command for permissions cleanup if applicable
+                       if (
+                               !$this->isWindows &&
+                               $fileOpHandle->chmodPath !== null &&
+                               strlen( $octalPermissions ) == 4
+                       ) {
+                               $encPath = escapeshellarg( $fileOpHandle->chmodPath );
+                               $cmd .= " && chmod $octalPermissions $encPath 2>/dev/null";
+                       }
+                       $pipes[$index] = popen( $cmd, 'r' );
                }
 
                $errs = [];
@@ -772,12 +772,10 @@ class FSFileBackend extends FileBackendStore {
                        $function = $fileOpHandle->call;
                        $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
                        $statuses[$index] = $status;
-                       if ( $status->isOK() && $fileOpHandle->chmodPath ) {
-                               $this->chmod( $fileOpHandle->chmodPath );
-                       }
                }
 
                clearstatcache(); // files changed
+
                return $statuses;
        }
 
@@ -788,13 +786,52 @@ class FSFileBackend extends FileBackendStore {
         * @return bool Success
         */
        protected function chmod( $path ) {
-               $this->trapWarnings();
+               if ( $this->isWindows ) {
+                       return true;
+               }
+
+               AtEase::suppressWarnings();
                $ok = chmod( $path, $this->fileMode );
-               $this->untrapWarnings();
+               AtEase::restoreWarnings();
+
+               return $ok;
+       }
+
+       /**
+        * Unlink a file, suppressing the warnings
+        *
+        * @param string $path Absolute file system path
+        * @return bool Success
+        */
+       protected function unlink( $path ) {
+               AtEase::suppressWarnings();
+               $ok = unlink( $path );
+               AtEase::restoreWarnings();
 
                return $ok;
        }
 
+       /**
+        * @param array $params Operation parameters with 'content' and 'headers' fields
+        * @return TempFSFile|null
+        */
+       protected function stageContentAsTempFile( array $params ) {
+               $content = $params['content'];
+               $tempFile = $this->tmpFileFactory->newTempFSFile( 'create_', 'tmp' );
+               if ( !$tempFile ) {
+                       return null;
+               }
+
+               AtEase::suppressWarnings();
+               $tmpPath = $tempFile->getPath();
+               if ( file_put_contents( $tmpPath, $content ) === false ) {
+                       $tempFile = null;
+               }
+               AtEase::restoreWarnings();
+
+               return $tempFile;
+       }
+
        /**
         * Return the text of an index.html file to hide directory listings
         *
@@ -824,30 +861,29 @@ class FSFileBackend extends FileBackendStore {
        }
 
        /**
-        * Listen for E_WARNING errors and track whether any happen
+        * Listen for E_WARNING errors and track whether any that happen
+        *
+        * @param string|null $regexIgnore Optional regex of errors to ignore
         */
-       protected function trapWarnings() {
-               // push to stack
-               $this->hadWarningErrors[] = false;
-               set_error_handler( function ( $errno, $errstr ) {
-                       // more detailed error logging
-                       $this->logger->error( $errstr );
-                       $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
-
-                       // suppress from PHP handler
-                       return true;
+       protected function trapWarnings( $regexIgnore = null ) {
+               $this->warningTrapStack[] = false;
+               set_error_handler( function ( $errno, $errstr ) use ( $regexIgnore ) {
+                       if ( $regexIgnore === null || !preg_match( $regexIgnore, $errstr ) ) {
+                               $this->logger->error( $errstr );
+                               $this->warningTrapStack[count( $this->warningTrapStack ) - 1] = true;
+                       }
+                       return true; // suppress from PHP handler
                }, E_WARNING );
        }
 
        /**
-        * Stop listening for E_WARNING errors and return true if any happened
+        * Stop listening for E_WARNING errors and get whether any happened
         *
-        * @return bool
+        * @return bool Whether any warnings happened
         */
        protected function untrapWarnings() {
-               // restore previous handler
                restore_error_handler();
-               // pop from stack
-               return array_pop( $this->hadWarningErrors );
+
+               return array_pop( $this->warningTrapStack );
        }
 }
index f2c07e8..5caf250 100644 (file)
@@ -1587,7 +1587,7 @@ abstract class FileBackendStore extends FileBackend {
                                // Validate and sanitize the relative path (backend-specific)
                                $relPath = $this->resolveContainerPath( $shortCont, $relPath );
                                if ( $relPath !== null ) {
-                                       // Prepend any wiki ID prefix to the container name
+                                       // Prepend any domain ID prefix to the container name
                                        $container = $this->fullContainerName( $shortCont );
                                        if ( self::isValidContainerName( $container ) ) {
                                                // Validate and sanitize the container name (backend-specific)
@@ -1722,7 +1722,7 @@ abstract class FileBackendStore extends FileBackend {
        }
 
        /**
-        * Get the full container name, including the wiki ID prefix
+        * Get the full container name, including the domain ID prefix
         *
         * @param string $container
         * @return string
index f3bbecb..82f1962 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use Wikimedia\AtEase\AtEase;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
  * Simulation of a backend storage in memory.
@@ -56,7 +57,7 @@ class MemoryFileBackend extends FileBackendStore {
 
                $this->files[$dst] = [
                        'data' => $params['content'],
-                       'mtime' => wfTimestamp( TS_MW, time() )
+                       'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
                ];
 
                return $status;
@@ -83,7 +84,7 @@ class MemoryFileBackend extends FileBackendStore {
 
                $this->files[$dst] = [
                        'data' => $data,
-                       'mtime' => wfTimestamp( TS_MW, time() )
+                       'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
                ];
 
                return $status;
@@ -116,7 +117,7 @@ class MemoryFileBackend extends FileBackendStore {
 
                $this->files[$dst] = [
                        'data' => $this->files[$src]['data'],
-                       'mtime' => wfTimestamp( TS_MW, time() )
+                       'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
                ];
 
                return $status;
index 56a2177..edea75f 100644 (file)
@@ -146,9 +146,9 @@ class SwiftFileBackend extends FileBackendStore {
 
        public function getFeatures() {
                return (
-                       FileBackend::ATTR_UNICODE_PATHS |
-                       FileBackend::ATTR_HEADERS |
-                       FileBackend::ATTR_METADATA
+                       self::ATTR_UNICODE_PATHS |
+                       self::ATTR_HEADERS |
+                       self::ATTR_METADATA
                );
        }
 
@@ -1299,6 +1299,9 @@ class SwiftFileBackend extends FileBackendStore {
         * @return StatusValue[]
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
+               /** @var SwiftFileOpHandle[] $fileOpHandles */
+               '@phan-var SwiftFileOpHandle[] $fileOpHandles';
+
                /** @var StatusValue[] $statuses */
                $statuses = [];
 
@@ -1314,7 +1317,6 @@ class SwiftFileBackend extends FileBackendStore {
                // Split the HTTP requests into stages that can be done concurrently
                $httpReqsByStage = []; // map of (stage => index => HTTP request)
                foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       /** @var SwiftFileOpHandle $fileOpHandle */
                        $reqs = $fileOpHandle->httpOp;
                        // Convert the 'url' parameter to an actual URL using $auth
                        foreach ( $reqs as $stage => &$req ) {
index c366a0f..8697f9f 100644 (file)
@@ -34,8 +34,8 @@ abstract class FileBackendStoreOpHandle {
        public $backend;
        /** @var array */
        public $resourcesToClose = [];
-
-       public $call; // string; name that identifies the function called
+       /** @var callable name that identifies the function called */
+       public $call;
 
        /**
         * Close all open file handles
index 1937e37..cce32ba 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use Wikimedia\AtEase\AtEase;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
  * Class representing a non-directory file on the file system
@@ -68,7 +69,11 @@ class FSFile {
         * @return int|bool
         */
        public function getSize() {
-               return filesize( $this->path );
+               AtEase::suppressWarnings();
+               $size = filesize( $this->path );
+               AtEase::restoreWarnings();
+
+               return $size;
        }
 
        /**
@@ -81,7 +86,7 @@ class FSFile {
                $timestamp = filemtime( $this->path );
                AtEase::restoreWarnings();
                if ( $timestamp !== false ) {
-                       $timestamp = wfTimestamp( TS_MW, $timestamp );
+                       $timestamp = ConvertibleTimestamp::convert( TS_MW, $timestamp );
                }
 
                return $timestamp;
index 0f11059..b195a08 100644 (file)
@@ -161,6 +161,8 @@ class MultiHttpClient implements LoggerAwareInterface {
         */
        public function runMulti( array $reqs, array $opts = [] ) {
                $this->normalizeRequests( $reqs );
+               $opts += [ 'connTimeout' => $this->connTimeout, 'reqTimeout' => $this->reqTimeout ];
+
                if ( $this->isCurlEnabled() ) {
                        return $this->runMultiCurl( $reqs, $opts );
                } else {
@@ -195,7 +197,7 @@ class MultiHttpClient implements LoggerAwareInterface {
         * @throws Exception
         * @suppress PhanTypeInvalidDimOffset
         */
-       private function runMultiCurl( array $reqs, array $opts = [] ) {
+       private function runMultiCurl( array $reqs, array $opts ) {
                $chm = $this->getCurlMulti();
 
                $selectTimeout = $this->getSelectTimeout( $opts );
@@ -292,21 +294,21 @@ class MultiHttpClient implements LoggerAwareInterface {
 
        /**
         * @param array &$req HTTP request map
+        * @codingStandardsIgnoreStart
+        * @phan-param array{url:string,proxy?:?string,query:mixed,method:string,body:string|resource,headers:string[],stream?:resource,flags:array} $req
+        * @codingStandardsIgnoreEnd
         * @param array $opts
-        *   - connTimeout    : default connection timeout
-        *   - reqTimeout     : default request timeout
+        *   - connTimeout : default connection timeout
+        *   - reqTimeout : default request timeout
         * @return resource
         * @throws Exception
-        * @suppress PhanTypeMismatchArgumentInternal
         */
-       protected function getCurlHandle( array &$req, array $opts = [] ) {
+       protected function getCurlHandle( array &$req, array $opts ) {
                $ch = curl_init();
 
-               curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT_MS,
-                       ( $opts['connTimeout'] ?? $this->connTimeout ) * 1000 );
                curl_setopt( $ch, CURLOPT_PROXY, $req['proxy'] ?? $this->proxy );
-               curl_setopt( $ch, CURLOPT_TIMEOUT_MS,
-                       ( $opts['reqTimeout'] ?? $this->reqTimeout ) * 1000 );
+               curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT_MS, intval( $opts['connTimeout'] * 1e3 ) );
+               curl_setopt( $ch, CURLOPT_TIMEOUT_MS, intval( $opts['reqTimeout'] * 1e3 ) );
                curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
                curl_setopt( $ch, CURLOPT_MAXREDIRS, 4 );
                curl_setopt( $ch, CURLOPT_HEADER, 0 );
@@ -322,11 +324,8 @@ class MultiHttpClient implements LoggerAwareInterface {
                        $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
                }
                curl_setopt( $ch, CURLOPT_URL, $url );
-
                curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $req['method'] );
-               if ( $req['method'] === 'HEAD' ) {
-                       curl_setopt( $ch, CURLOPT_NOBODY, 1 );
-               }
+               curl_setopt( $ch, CURLOPT_NOBODY, ( $req['method'] === 'HEAD' ) );
 
                if ( $req['method'] === 'PUT' ) {
                        curl_setopt( $ch, CURLOPT_PUT, 1 );
@@ -406,23 +405,20 @@ class MultiHttpClient implements LoggerAwareInterface {
                        }
                );
 
-               if ( isset( $req['stream'] ) ) {
-                       // Don't just use CURLOPT_FILE as that might give:
-                       // curl_setopt(): cannot represent a stream of type Output as a STDIO FILE*
-                       // The callback here handles both normal files and php://temp handles.
-                       curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
-                               function ( $ch, $data ) use ( &$req ) {
+               // This works with both file and php://temp handles (unlike CURLOPT_FILE)
+               $hasOutputStream = isset( $req['stream'] );
+               curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
+                       function ( $ch, $data ) use ( &$req, $hasOutputStream ) {
+                               if ( $hasOutputStream ) {
                                        return fwrite( $req['stream'], $data );
-                               }
-                       );
-               } else {
-                       curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
-                               function ( $ch, $data ) use ( &$req ) {
+                               } else {
+                                       // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
                                        $req['response']['body'] .= $data;
+
                                        return strlen( $data );
                                }
-                       );
-               }
+                       }
+               );
 
                return $ch;
        }
index a090e16..629d2cd 100644 (file)
@@ -125,7 +125,7 @@ class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInt
        /** @var callable|null Function that takes a WAN cache callback and runs it later */
        protected $asyncHandler;
 
-       /** @bar bool Whether to use mcrouter key prefixing for routing */
+       /** @var bool Whether to use mcrouter key prefixing for routing */
        protected $mcrouterAware;
        /** @var string Physical region for mcrouter use */
        protected $region;
index f0b135f..7870f69 100644 (file)
@@ -280,7 +280,7 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function close() {
+       public function close( $fname = __METHOD__, $owner = null ) {
                throw new DBUnexpectedError( $this->conn, 'Cannot close shared connection.' );
        }
 
index be41ee0..51596da 100644 (file)
@@ -174,6 +174,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        /** @var float Query rount trip time estimate */
        private $lastRoundTripEstimate = 0.0;
 
+       /** @var int|null Integer ID of the managing LBFactory instance or null if none */
+       private $ownerId;
+
        /** @var string Lock granularity is on the level of the entire database */
        const ATTR_DB_LEVEL_LOCKING = 'db-level-locking';
        /** @var string The SCHEMA keyword refers to a grouping of tables in a database */
@@ -268,6 +271,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $params['schema'] != '' ? $params['schema'] : null,
                        $params['tablePrefix']
                );
+
+               $this->ownerId = $params['ownerId'] ?? null;
        }
 
        /**
@@ -355,7 +360,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         *   - 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.
-        *   - nonNativeInsertSelectBatchSize: Optional batch size for non-native INSERT SELECT emulation.
+        *   - nonNativeInsertSelectBatchSize: Optional batch size for non-native INSERT SELECT.
+        *   - ownerId: Optional integer ID of a LoadBalancer instance that manages this instance.
         * @param int $connect One of the class constants (NEW_CONNECTED, NEW_UNCONNECTED) [optional]
         * @return Database|null If the database driver or extension cannot be found
         * @throws InvalidArgumentException If the database driver or extension cannot be found
@@ -375,7 +381,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                'flags' => 0,
                                'variables' => [],
                                'cliMode' => ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ),
-                               'agent' => basename( $_SERVER['SCRIPT_NAME'] ) . '@' . gethostname()
+                               'agent' => basename( $_SERVER['SCRIPT_NAME'] ) . '@' . gethostname(),
+                               'ownerId' => null
                        ];
 
                        $normalizedParams = [
@@ -866,8 +873,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                );
        }
 
-       final public function close() {
-               $exception = null; // error to throw after disconnecting
+       final public function close( $fname = __METHOD__, $owner = null ) {
+               $error = null; // error to throw after disconnecting
 
                $wasOpen = (bool)$this->conn;
                // This should mostly do nothing if the connection is already closed
@@ -877,34 +884,22 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                if ( $this->trxAtomicLevels ) {
                                        // Cannot let incomplete atomic sections be committed
                                        $levels = $this->flatAtomicSectionList();
-                                       $exception = new DBUnexpectedError(
-                                               $this,
-                                               __METHOD__ . ": atomic sections $levels are still open"
-                                       );
+                                       $error = "$fname: atomic sections $levels are still open";
                                } elseif ( $this->trxAutomatic ) {
                                        // Only the connection manager can commit non-empty DBO_TRX transactions
                                        // (empty ones we can silently roll back)
                                        if ( $this->writesOrCallbacksPending() ) {
-                                               $exception = new DBUnexpectedError(
-                                                       $this,
-                                                       __METHOD__ .
-                                                       ": mass commit/rollback of peer transaction required (DBO_TRX set)"
-                                               );
+                                               $error = "$fname: " .
+                                                       "expected mass rollback of all peer transactions (DBO_TRX set)";
                                        }
                                } else {
                                        // Manual transactions should have been committed or rolled
                                        // back, even if empty.
-                                       $exception = new DBUnexpectedError(
-                                               $this,
-                                               __METHOD__ . ": transaction is still open (from {$this->trxFname})"
-                                       );
+                                       $error = "$fname: transaction is still open (from {$this->trxFname})";
                                }
 
-                               if ( $this->trxEndCallbacksSuppressed ) {
-                                       $exception = $exception ?: new DBUnexpectedError(
-                                               $this,
-                                               __METHOD__ . ': callbacks are suppressed; cannot properly commit'
-                                       );
+                               if ( $this->trxEndCallbacksSuppressed && $error === null ) {
+                                       $error = "$fname: callbacks are suppressed; cannot properly commit";
                                }
 
                                // Rollback the changes and run any callbacks as needed
@@ -919,9 +914,16 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
                $this->conn = null;
 
-               // Throw any unexpected errors after having disconnected
-               if ( $exception instanceof Exception ) {
-                       throw $exception;
+               // Log or throw any unexpected errors after having disconnected
+               if ( $error !== null ) {
+                       // T217819, T231443: if this is probably just LoadBalancer trying to recover from
+                       // errors and shutdown, then log any problems and move on since the request has to
+                       // end one way or another. Throwing errors is not very useful at some point.
+                       if ( $this->ownerId !== null && $owner === $this->ownerId ) {
+                               $this->queryLogger->error( $error );
+                       } else {
+                               throw new DBUnexpectedError( $this, $error );
+                       }
                }
 
                // Note that various subclasses call close() at the start of open(), which itself is
@@ -3981,17 +3983,17 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                if ( $this->trxLevel() ) {
                        if ( $this->trxAtomicLevels ) {
                                $levels = $this->flatAtomicSectionList();
-                               $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open";
+                               $msg = "$fname: got explicit BEGIN while atomic section(s) $levels are open";
                                throw new DBUnexpectedError( $this, $msg );
                        } elseif ( !$this->trxAutomatic ) {
-                               $msg = "$fname: Explicit transaction already active (from {$this->trxFname})";
+                               $msg = "$fname: explicit transaction already active (from {$this->trxFname})";
                                throw new DBUnexpectedError( $this, $msg );
                        } else {
-                               $msg = "$fname: Implicit transaction already active (from {$this->trxFname})";
+                               $msg = "$fname: implicit transaction already active (from {$this->trxFname})";
                                throw new DBUnexpectedError( $this, $msg );
                        }
                } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
-                       $msg = "$fname: Implicit transaction expected (DBO_TRX set)";
+                       $msg = "$fname: implicit transaction expected (DBO_TRX set)";
                        throw new DBUnexpectedError( $this, $msg );
                }
 
@@ -4045,7 +4047,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $levels = $this->flatAtomicSectionList();
                        throw new DBUnexpectedError(
                                $this,
-                               "$fname: Got COMMIT while atomic sections $levels are still open"
+                               "$fname: got COMMIT while atomic sections $levels are still open"
                        );
                }
 
@@ -4055,17 +4057,17 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        } elseif ( !$this->trxAutomatic ) {
                                throw new DBUnexpectedError(
                                        $this,
-                                       "$fname: Flushing an explicit transaction, getting out of sync"
+                                       "$fname: flushing an explicit transaction, getting out of sync"
                                );
                        }
                } elseif ( !$this->trxLevel() ) {
                        $this->queryLogger->error(
-                               "$fname: No transaction to commit, something got out of sync" );
+                               "$fname: no transaction to commit, something got out of sync" );
                        return; // nothing to do
                } elseif ( $this->trxAutomatic ) {
                        throw new DBUnexpectedError(
                                $this,
-                               "$fname: Expected mass commit of all peer transactions (DBO_TRX set)"
+                               "$fname: expected mass commit of all peer transactions (DBO_TRX set)"
                        );
                }
 
index 106772b..b9a1af6 100644 (file)
@@ -26,6 +26,7 @@ use mysqli;
 use mysqli_result;
 use IP;
 use stdClass;
+use Wikimedia\AtEase\AtEase;
 
 /**
  * Database abstraction object for PHP extension mysqli.
@@ -41,7 +42,11 @@ class DatabaseMysqli extends DatabaseMysqlBase {
         * @return mysqli_result|bool
         */
        protected function doQuery( $sql ) {
-               return $this->getBindingHandle()->query( $sql );
+               AtEase::suppressWarnings();
+               $res = $this->getBindingHandle()->query( $sql );
+               AtEase::restoreWarnings();
+
+               return $res;
        }
 
        /**
index 41f7cb6..befc80c 100644 (file)
@@ -460,10 +460,12 @@ interface IDatabase {
         * aside from read-only automatic transactions (assuming no callbacks are registered).
         * If a transaction is still open anyway, it will be rolled back.
         *
+        * @param string $fname Caller name
+        * @param int|null $owner ID of the calling instance (e.g. the LBFactory ID)
         * @return bool Success
         * @throws DBError
         */
-       public function close();
+       public function close( $fname = __METHOD__, $owner = null );
 
        /**
         * Run an SQL query and return the result
index 6e9591b..23232f4 100644 (file)
@@ -109,9 +109,10 @@ interface ILBFactory {
         * but still use DBO_TRX transaction rounds on other tables.
         *
         * @param bool|string $domain Domain ID, or false for the current domain
+        * @param int|null $owner Owner ID of the new instance (e.g. this LBFactory ID)
         * @return ILoadBalancer
         */
-       public function newMainLB( $domain = false );
+       public function newMainLB( $domain = false, $owner = null );
 
        /**
         * Get a cached (tracked) load balancer object.
@@ -131,9 +132,10 @@ interface ILBFactory {
         * (DBO_TRX off) but still use DBO_TRX transaction rounds on other tables.
         *
         * @param string $cluster External storage cluster name
+        * @param int|null $owner Owner ID of the new instance (e.g. this LBFactory ID)
         * @return ILoadBalancer
         */
-       public function newExternalLB( $cluster );
+       public function newExternalLB( $cluster, $owner = null );
 
        /**
         * Get a cached (tracked) load balancer for external storage
index 36e961a..07a5fe6 100644 (file)
@@ -155,15 +155,16 @@ abstract class LBFactory implements ILBFactory {
                $this->defaultGroup = $conf['defaultGroup'] ?? null;
                $this->secret = $conf['secret'] ?? '';
 
-               $this->id = mt_rand();
-               $this->ticket = mt_rand();
+               static $nextId, $nextTicket;
+               $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
+               $this->ticket = $nextTicket = ( is_int( $nextTicket ) ? $nextTicket++ : mt_rand() );
        }
 
        public function destroy() {
                /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = ScopedCallback::newScopedIgnoreUserAbort();
 
-               $this->forEachLBCallMethod( 'disable' );
+               $this->forEachLBCallMethod( 'disable', [ __METHOD__, $this->id ] );
        }
 
        public function getLocalDomainID() {
@@ -195,34 +196,6 @@ abstract class LBFactory implements ILBFactory {
                $this->commitMasterChanges( __METHOD__ ); // sanity
        }
 
-       /**
-        * @see ILBFactory::newMainLB()
-        * @param bool $domain
-        * @return ILoadBalancer
-        */
-       abstract public function newMainLB( $domain = false );
-
-       /**
-        * @see ILBFactory::getMainLB()
-        * @param bool $domain
-        * @return ILoadBalancer
-        */
-       abstract public function getMainLB( $domain = false );
-
-       /**
-        * @see ILBFactory::newExternalLB()
-        * @param string $cluster
-        * @return ILoadBalancer
-        */
-       abstract public function newExternalLB( $cluster );
-
-       /**
-        * @see ILBFactory::getExternalLB()
-        * @param string $cluster
-        * @return ILoadBalancer
-        */
-       abstract public function getExternalLB( $cluster );
-
        /**
         * Call a method of each tracked load balancer
         *
@@ -245,13 +218,13 @@ abstract class LBFactory implements ILBFactory {
                                [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
                        );
                }
-               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
+               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
        }
 
        final public function commitAll( $fname = __METHOD__, array $options = [] ) {
                $this->commitMasterChanges( $fname, $options );
-               $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname ] );
-               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
+               $this->forEachLBCallMethod( 'flushMasterSnapshots', [ $fname, $this->id ] );
+               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname, $this->id ] );
        }
 
        final public function beginMasterChanges( $fname = __METHOD__ ) {
@@ -604,10 +577,12 @@ abstract class LBFactory implements ILBFactory {
        }
 
        /**
-        * Base parameters to ILoadBalancer::__construct()
+        * Get parameters to ILoadBalancer::__construct()
+        *
+        * @param int|null $owner Use getOwnershipId() if this is for getMainLB()/getExternalLB()
         * @return array
         */
-       final protected function baseLoadBalancerParams() {
+       final protected function baseLoadBalancerParams( $owner ) {
                if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
                        $initStage = ILoadBalancer::STAGE_POSTCOMMIT_CALLBACKS;
                } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
@@ -639,7 +614,7 @@ abstract class LBFactory implements ILBFactory {
                                $this->getChronologyProtector()->applySessionReplicationPosition( $lb );
                        },
                        'roundStage' => $initStage,
-                       'ownerId' => $this->id
+                       'ownerId' => $owner
                ];
        }
 
@@ -689,7 +664,7 @@ abstract class LBFactory implements ILBFactory {
                /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = ScopedCallback::newScopedIgnoreUserAbort();
 
-               $this->forEachLBCallMethod( 'closeAll' );
+               $this->forEachLBCallMethod( 'closeAll', [ __METHOD__, $this->id ] );
        }
 
        public function setAgentName( $agent ) {
@@ -757,6 +732,14 @@ abstract class LBFactory implements ILBFactory {
                $this->requestInfo = $info + $this->requestInfo;
        }
 
+       /**
+        * @return int Internal instance ID used to assert ownership of ILoadBalancer instances
+        * @since 1.34
+        */
+       final protected function getOwnershipId() {
+               return $this->id;
+       }
+
        /**
         * @param string $stage
         */
index ef1f0a6..77b029f 100644 (file)
@@ -157,7 +157,7 @@ class LBFactoryMulti extends LBFactory {
                $this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class;
        }
 
-       public function newMainLB( $domain = false ) {
+       public function newMainLB( $domain = false, $owner = null ) {
                $section = $this->getSectionForDomain( $domain );
                if ( !isset( $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] ) ) {
                        throw new UnexpectedValueException( "Section '$section' has no hosts defined." );
@@ -175,7 +175,8 @@ class LBFactoryMulti extends LBFactory {
                        // Use the LB-specific read-only reason if everything isn't already read-only
                        is_string( $this->readOnlyReason )
                                ? $this->readOnlyReason
-                               : ( $this->readOnlyBySection[$section] ?? false )
+                               : ( $this->readOnlyBySection[$section] ?? false ),
+                       $owner
                );
        }
 
@@ -183,13 +184,13 @@ class LBFactoryMulti extends LBFactory {
                $section = $this->getSectionForDomain( $domain );
 
                if ( !isset( $this->mainLBs[$section] ) ) {
-                       $this->mainLBs[$section] = $this->newMainLB( $domain );
+                       $this->mainLBs[$section] = $this->newMainLB( $domain, $this->getOwnershipId() );
                }
 
                return $this->mainLBs[$section];
        }
 
-       public function newExternalLB( $cluster ) {
+       public function newExternalLB( $cluster, $owner = null ) {
                if ( !isset( $this->externalLoads[$cluster] ) ) {
                        throw new InvalidArgumentException( "Unknown cluster '$cluster'" );
                }
@@ -201,13 +202,15 @@ class LBFactoryMulti extends LBFactory {
                                $this->templateOverridesByCluster[$cluster] ?? []
                        ),
                        [ ILoadBalancer::GROUP_GENERIC => $this->externalLoads[$cluster] ],
-                       $this->readOnlyReason
+                       $this->readOnlyReason,
+                       $owner
                );
        }
 
        public function getExternalLB( $cluster ) {
                if ( !isset( $this->externalLBs[$cluster] ) ) {
-                       $this->externalLBs[$cluster] = $this->newExternalLB( $cluster );
+                       $this->externalLBs[$cluster] =
+                               $this->newExternalLB( $cluster, $this->getOwnershipId() );
                }
 
                return $this->externalLBs[$cluster];
@@ -265,11 +268,12 @@ class LBFactoryMulti extends LBFactory {
         * @param array $serverTemplate Server config map
         * @param int[][] $groupLoads Map of (group => host => load)
         * @param string|bool $readOnlyReason
+        * @param int|null $owner
         * @return LoadBalancer
         */
-       private function newLoadBalancer( $serverTemplate, $groupLoads, $readOnlyReason ) {
+       private function newLoadBalancer( $serverTemplate, $groupLoads, $readOnlyReason, $owner ) {
                $lb = new LoadBalancer( array_merge(
-                       $this->baseLoadBalancerParams(),
+                       $this->baseLoadBalancerParams( $owner ),
                        [
                                'servers' => $this->makeServerArray( $serverTemplate, $groupLoads ),
                                'loadMonitor' => [ 'class' => $this->loadMonitorClass ],
index 7e73e5b..cc79f99 100644 (file)
@@ -75,29 +75,29 @@ class LBFactorySimple extends LBFactory {
                $this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class;
        }
 
-       public function newMainLB( $domain = false ) {
-               return $this->newLoadBalancer( $this->mainServers );
+       public function newMainLB( $domain = false, $owner = null ) {
+               return $this->newLoadBalancer( $this->mainServers, $owner );
        }
 
        public function getMainLB( $domain = false ) {
                if ( $this->mainLB === null ) {
-                       $this->mainLB = $this->newMainLB( $domain );
+                       $this->mainLB = $this->newMainLB( $domain, $this->getOwnershipId() );
                }
 
                return $this->mainLB;
        }
 
-       public function newExternalLB( $cluster ) {
+       public function newExternalLB( $cluster, $owner = null ) {
                if ( !isset( $this->externalServersByCluster[$cluster] ) ) {
                        throw new InvalidArgumentException( "Unknown cluster '$cluster'." );
                }
 
-               return $this->newLoadBalancer( $this->externalServersByCluster[$cluster] );
+               return $this->newLoadBalancer( $this->externalServersByCluster[$cluster], $owner );
        }
 
        public function getExternalLB( $cluster ) {
                if ( !isset( $this->externalLBs[$cluster] ) ) {
-                       $this->externalLBs[$cluster] = $this->newExternalLB( $cluster );
+                       $this->externalLBs[$cluster] = $this->newExternalLB( $cluster, $this->getOwnershipId() );
                }
 
                return $this->externalLBs[$cluster];
@@ -116,9 +116,9 @@ class LBFactorySimple extends LBFactory {
                return $lbs;
        }
 
-       private function newLoadBalancer( array $servers ) {
+       private function newLoadBalancer( array $servers, $owner ) {
                $lb = new LoadBalancer( array_merge(
-                       $this->baseLoadBalancerParams(),
+                       $this->baseLoadBalancerParams( $owner ),
                        [
                                'servers' => $servers,
                                'loadMonitor' => [ 'class' => $this->loadMonitorClass ],
index 60044ba..97daf10 100644 (file)
@@ -44,8 +44,12 @@ class LBFactorySingle extends LBFactory {
                        throw new InvalidArgumentException( "Missing 'connection' argument." );
                }
 
-               $lb = new LoadBalancerSingle( array_merge( $this->baseLoadBalancerParams(), $conf ) );
+               $lb = new LoadBalancerSingle( array_merge(
+                       $this->baseLoadBalancerParams( $this->getOwnershipId() ),
+                       $conf
+               ) );
                $this->initLoadBalancer( $lb );
+
                $this->lb = $lb;
        }
 
@@ -63,23 +67,15 @@ class LBFactorySingle extends LBFactory {
                ) );
        }
 
-       /**
-        * @param bool|string $domain (unused)
-        * @return LoadBalancerSingle
-        */
-       public function newMainLB( $domain = false ) {
-               return $this->lb;
+       public function newMainLB( $domain = false, $owner = null ) {
+               throw new BadMethodCallException( "Method is not supported." );
        }
 
-       /**
-        * @param bool|string $domain (unused)
-        * @return LoadBalancerSingle
-        */
        public function getMainLB( $domain = false ) {
                return $this->lb;
        }
 
-       public function newExternalLB( $cluster ) {
+       public function newExternalLB( $cluster, $owner = null ) {
                throw new BadMethodCallException( "Method is not supported." );
        }
 
@@ -87,24 +83,14 @@ class LBFactorySingle extends LBFactory {
                throw new BadMethodCallException( "Method is not supported." );
        }
 
-       /**
-        * @return LoadBalancerSingle[] Map of (cluster name => LoadBalancer)
-        */
        public function getAllMainLBs() {
                return [ 'DEFAULT' => $this->lb ];
        }
 
-       /**
-        * @return LoadBalancerSingle[] Map of (cluster name => LoadBalancer)
-        */
        public function getAllExternalLBs() {
                return [];
        }
 
-       /**
-        * @param string|callable $callback
-        * @param array $params
-        */
        public function forEachLB( $callback, array $params = [] ) {
                if ( isset( $this->lb ) ) { // may not be set during _destruct()
                        $callback( $this->lb, ...$params );
index 160d501..4ca4250 100644 (file)
@@ -493,15 +493,22 @@ interface ILoadBalancer {
        public function getReplicaResumePos();
 
        /**
-        * Disable this load balancer. All connections are closed, and any attempt to
-        * open a new connection will result in a DBAccessError.
+        * Close all connections and disable this load balancer
+        *
+        * Any attempt to open a new connection will result in a DBAccessError.
+        *
+        * @param string $fname Caller name
+        * @param int|null $owner ID of the calling instance (e.g. the LBFactory ID)
         */
-       public function disable();
+       public function disable( $fname = __METHOD__, $owner = null );
 
        /**
         * Close all open connections
+        *
+        * @param string $fname Caller name
+        * @param int|null $owner ID of the calling instance (e.g. the LBFactory ID)
         */
-       public function closeAll();
+       public function closeAll( $fname = __METHOD__, $owner = null );
 
        /**
         * Close a connection
@@ -598,8 +605,9 @@ interface ILoadBalancer {
         * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshots
         *
         * @param string $fname Caller name
+        * @param int|null $owner ID of the calling instance (e.g. the LBFactory ID)
         */
-       public function flushReplicaSnapshots( $fname = __METHOD__ );
+       public function flushReplicaSnapshots( $fname = __METHOD__, $owner = null );
 
        /**
         * Commit all master DB transactions so as to flush any REPEATABLE-READ or SSI snapshots
@@ -607,8 +615,9 @@ interface ILoadBalancer {
         * An error will be thrown if a connection has pending writes or callbacks
         *
         * @param string $fname Caller name
+        * @param int|null $owner ID of the calling instance (e.g. the LBFactory ID)
         */
-       public function flushMasterSnapshots( $fname = __METHOD__ );
+       public function flushMasterSnapshots( $fname = __METHOD__, $owner = null );
 
        /**
         * @return bool Whether a master connection is already open
index f60e8db..39d1bd6 100644 (file)
@@ -106,6 +106,10 @@ class LoadBalancer implements ILoadBalancer {
        /** @var bool[] Map of (domain => whether to use "temp tables only" mode) */
        private $tempTablesOnlyMode = [];
 
+       /** @var string|bool Explicit DBO_TRX transaction round active or false if none */
+       private $trxRoundId = false;
+       /** @var string Stage of the current transaction round in the transaction round life-cycle */
+       private $trxRoundStage = self::ROUND_CURSORY;
        /** @var Database Connection handle that caused a problem */
        private $errorConnection;
        /** @var int[] The group replica server indexes keyed by group */
@@ -125,12 +129,10 @@ class LoadBalancer implements ILoadBalancer {
        /** @var bool Whether any connection has been attempted yet */
        private $connectionAttempted = false;
 
+       /** var int An identifier for this class instance */
+       private $id;
        /** @var int|null Integer ID of the managing LBFactory instance or null if none */
        private $ownerId;
-       /** @var string|bool Explicit DBO_TRX transaction round active or false if none */
-       private $trxRoundId = false;
-       /** @var string Stage of the current transaction round in the transaction round life-cycle */
-       private $trxRoundStage = self::ROUND_CURSORY;
 
        /** @var int Warn when this many connection are held */
        const CONN_HELD_WARN_THRESHOLD = 10;
@@ -218,7 +220,6 @@ class LoadBalancer implements ILoadBalancer {
                $this->deprecationLogger = $params['deprecationLogger'] ?? function ( $msg ) {
                        trigger_error( $msg, E_USER_DEPRECATED );
                };
-
                foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
                        $this->$key = $params[$key] ?? new NullLogger();
                }
@@ -242,6 +243,8 @@ class LoadBalancer implements ILoadBalancer {
                $group = $params['defaultGroup'] ?? self::GROUP_GENERIC;
                $this->defaultGroup = isset( $this->groupLoads[$group] ) ? $group : self::GROUP_GENERIC;
 
+               static $nextId;
+               $this->id = $nextId = ( is_int( $nextId ) ? $nextId++ : mt_rand() );
                $this->ownerId = $params['ownerId'] ?? null;
        }
 
@@ -1301,6 +1304,7 @@ class LoadBalancer implements ILoadBalancer {
                // Use DBO_DEFAULT flags by default for LoadBalancer managed databases. Assume that the
                // application calls LoadBalancer::commitMasterChanges() before the PHP script completes.
                $server['flags'] = $server['flags'] ?? IDatabase::DBO_DEFAULT;
+               $server['ownerId'] = $this->id;
 
                // Create a live connection object
                $conn = Database::factory( $server['type'], $server, Database::NEW_UNCONNECTED );
@@ -1498,23 +1502,22 @@ class LoadBalancer implements ILoadBalancer {
                return $highestPos;
        }
 
-       public function disable() {
-               $this->closeAll();
+       public function disable( $fname = __METHOD__, $owner = null ) {
+               $this->assertOwnership( $fname, $owner );
+               $this->closeAll( $fname, $owner );
                $this->disabled = true;
        }
 
-       public function closeAll() {
+       public function closeAll( $fname = __METHOD__, $owner = null ) {
+               $this->assertOwnership( $fname, $owner );
                if ( $this->ownerId === null ) {
                        /** @noinspection PhpUnusedLocalVariableInspection */
                        $scope = ScopedCallback::newScopedIgnoreUserAbort();
                }
-
-               $fname = __METHOD__;
                $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $fname ) {
                        $host = $conn->getServer();
-                       $this->connLogger->debug(
-                               $fname . ": closing connection to database '$host'." );
-                       $conn->close();
+                       $this->connLogger->debug( "$fname: closing connection to database '$host'." );
+                       $conn->close( $fname, $this->id );
                } );
 
                $this->conns = self::newTrackedConnectionsArray();
@@ -1543,13 +1546,13 @@ class LoadBalancer implements ILoadBalancer {
                        }
                }
 
-               $conn->close();
+               $conn->close( __METHOD__ );
        }
 
        public function commitAll( $fname = __METHOD__, $owner = null ) {
                $this->commitMasterChanges( $fname, $owner );
-               $this->flushMasterSnapshots( $fname );
-               $this->flushReplicaSnapshots( $fname );
+               $this->flushMasterSnapshots( $fname, $owner );
+               $this->flushReplicaSnapshots( $fname, $owner );
        }
 
        public function finalizeMasterChanges( $fname = __METHOD__, $owner = null ) {
@@ -1634,7 +1637,7 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                // Clear any empty transactions (no writes/callbacks) from the implicit round
-               $this->flushMasterSnapshots( $fname );
+               $this->flushMasterSnapshots( $fname, $owner );
 
                $this->trxRoundId = $fname;
                $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
@@ -1738,7 +1741,7 @@ class LoadBalancer implements ILoadBalancer {
                                        $this->queryLogger->warning( $fname . ": found writes pending." );
                                        $fnames = implode( ', ', $conn->pendingWriteAndCallbackCallers() );
                                        $this->queryLogger->warning(
-                                               $fname . ": found writes pending ($fnames).",
+                                               "$fname: found writes pending ($fnames).",
                                                [
                                                        'db_server' => $conn->getServer(),
                                                        'db_name' => $conn->getDBname()
@@ -1747,7 +1750,7 @@ class LoadBalancer implements ILoadBalancer {
                                } elseif ( $conn->trxLevel() ) {
                                        // A callback from another handle read from this one and DBO_TRX is set,
                                        // which can easily happen if there is only one DB (no replicas)
-                                       $this->queryLogger->debug( $fname . ": found empty transaction." );
+                                       $this->queryLogger->debug( "$fname: found empty transaction." );
                                }
                                try {
                                        $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
@@ -1838,15 +1841,22 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        /**
+        * Assure that if this instance is owned, the caller is either the owner or is internal
+        *
+        * If an LBFactory owns the LoadBalancer, then certain methods should only called through
+        * that LBFactory to avoid broken contracts. Otherwise, those methods can publically be
+        * called by anything. In any case, internal methods from the LoadBalancer itself should
+        * always be allowed.
+        *
         * @param string $fname
         * @param int|null $owner Owner ID of the caller
         * @throws DBTransactionError
         */
        private function assertOwnership( $fname, $owner ) {
-               if ( $this->ownerId !== null && $owner !== $this->ownerId ) {
+               if ( $this->ownerId !== null && $owner !== $this->ownerId && $owner !== $this->id ) {
                        throw new DBTransactionError(
                                null,
-                               "$fname: LoadBalancer is owned by LBFactory #{$this->ownerId} (got '$owner')."
+                               "$fname: LoadBalancer is owned by ID '{$this->ownerId}' (got '$owner')."
                        );
                }
        }
@@ -1893,13 +1903,15 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+       public function flushReplicaSnapshots( $fname = __METHOD__, $owner = null ) {
+               $this->assertOwnership( $fname, $owner );
                $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) use ( $fname ) {
                        $conn->flushSnapshot( $fname );
                } );
        }
 
-       public function flushMasterSnapshots( $fname = __METHOD__ ) {
+       public function flushMasterSnapshots( $fname = __METHOD__, $owner = null ) {
+               $this->assertOwnership( $fname, $owner );
                $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $fname ) {
                        $conn->flushSnapshot( $fname );
                } );
@@ -2317,7 +2329,7 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function redefineLocalDomain( $domain ) {
-               $this->closeAll();
+               $this->closeAll( __METHOD__, $this->id );
 
                $this->setLocalDomain( DatabaseDomain::newFromId( $domain ) );
        }
@@ -2379,7 +2391,7 @@ class LoadBalancer implements ILoadBalancer {
 
        function __destruct() {
                // Avoid connection leaks for sanity
-               $this->disable();
+               $this->disable( __METHOD__, $this->ownerId );
        }
 }
 
index 981aeb0..d5f5de3 100644 (file)
@@ -94,8 +94,7 @@ class LogPage {
 
                $dbw = wfGetDB( DB_MASTER );
 
-               // @todo FIXME private/protected/public property?
-               $this->timestamp = $now = wfTimestampNow();
+               $now = wfTimestampNow();
                $data = [
                        'log_type' => $this->type,
                        'log_action' => $this->action,
index 5326705..523a2f8 100644 (file)
@@ -253,8 +253,6 @@ class ManualLogEntry extends LogEntryBase implements Taggable {
         * @throws MWException
         */
        public function insert( IDatabase $dbw = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $dbw = $dbw ?: wfGetDB( DB_MASTER );
 
                if ( $this->timestamp === null ) {
@@ -267,31 +265,6 @@ class ManualLogEntry extends LogEntryBase implements Taggable {
                $params = $this->getParameters();
                $relations = $this->relations;
 
-               // Ensure actor relations are set
-               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
-                       empty( $relations['target_author_actor'] )
-               ) {
-                       $actorIds = [];
-                       if ( !empty( $relations['target_author_id'] ) ) {
-                               foreach ( $relations['target_author_id'] as $id ) {
-                                       $actorIds[] = User::newFromId( $id )->getActorId( $dbw );
-                               }
-                       }
-                       if ( !empty( $relations['target_author_ip'] ) ) {
-                               foreach ( $relations['target_author_ip'] as $ip ) {
-                                       $actorIds[] = User::newFromName( $ip, false )->getActorId( $dbw );
-                               }
-                       }
-                       if ( $actorIds ) {
-                               $relations['target_author_actor'] = $actorIds;
-                               $params['authorActors'] = $actorIds;
-                       }
-               }
-               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
-                       unset( $relations['target_author_id'], $relations['target_author_ip'] );
-                       unset( $params['authorIds'], $params['authorIPs'] );
-               }
-
                // Additional fields for which there's no space in the database table schema
                $revId = $this->getAssociatedRevId();
                if ( $revId ) {
index 2f6d4da..653e443 100644 (file)
@@ -987,6 +987,7 @@ EOT
                        parent::delete();
                        return;
                }
+               '@phan-var LocalFile $file';
 
                $deleter = new FileDeleteForm( $file );
                $deleter->execute();
index 9c5c4e0..bad75da 100644 (file)
@@ -726,7 +726,7 @@ class WikiPage implements Page, IDBAccessObject {
                // Try using the replica DB first, then try the master
                $rev = $this->mTitle->getFirstRevision();
                if ( !$rev ) {
-                       $rev = $this->mTitle->getFirstRevision( Title::GAID_FOR_UPDATE );
+                       $rev = $this->mTitle->getFirstRevision( Title::READ_LATEST );
                }
                return $rev;
        }
@@ -2841,7 +2841,7 @@ class WikiPage implements Page, IDBAccessObject {
         */
        protected function archiveRevisions( $dbw, $id, $suppress ) {
                global $wgContentHandlerUseDB, $wgMultiContentRevisionSchemaMigrationStage,
-                       $wgActorTableSchemaMigrationStage, $wgDeleteRevisionsBatchSize;
+                       $wgDeleteRevisionsBatchSize;
 
                // Given the lock above, we can be confident in the title and page ID values
                $namespace = $this->getTitle()->getNamespace();
@@ -2968,9 +2968,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                        $dbw->delete( 'revision', [ 'rev_id' => $revids ], __METHOD__ );
                        $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
-                       }
+                       $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
 
                        // Also delete records from ip_changes as applicable.
                        if ( count( $ipRevIds ) > 0 ) {
@@ -3147,7 +3145,7 @@ class WikiPage implements Page, IDBAccessObject {
        public function commitRollback( $fromP, $summary, $bot,
                &$resultDetails, User $guser, $tags = null
        ) {
-               global $wgUseRCPatrol;
+               global $wgUseRCPatrol, $wgDisableAnonTalk;
 
                $dbw = wfGetDB( DB_MASTER );
 
@@ -3220,6 +3218,8 @@ class WikiPage implements Page, IDBAccessObject {
                if ( empty( $summary ) ) {
                        if ( !$currentEditorForPublic ) { // no public user name
                                $summary = wfMessage( 'revertpage-nouser' );
+                       } elseif ( $wgDisableAnonTalk && $current->getUser() === 0 ) {
+                               $summary = wfMessage( 'revertpage-anon' );
                        } else {
                                $summary = wfMessage( 'revertpage' );
                        }
index 472bcdd..6af60a7 100644 (file)
@@ -22,7 +22,6 @@
  */
 
 use MediaWiki\Linker\LinkRenderer;
-use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Navigation\PrevNextNavigationRenderer;
 use Wikimedia\Rdbms\IDatabase;
@@ -796,14 +795,14 @@ abstract class IndexPager extends ContextSource implements Pager {
        /**
         * Generate (prev x| next x) (20|50|100...) type links for paging
         *
-        * @param LinkTarget $title
+        * @param Title $title
         * @param int $offset
         * @param int $limit
         * @param array $query Optional URL query parameter string
         * @param bool $atend Optional param for specified if this is the last page
         * @return string
         */
-       protected function buildPrevNextNavigation( LinkTarget $title, $offset, $limit,
+       protected function buildPrevNextNavigation( Title $title, $offset, $limit,
                                                                                                array $query = [], $atend = false
        ) {
                $prevNext = new PrevNextNavigationRenderer( $this );
index b56527a..6b63a0d 100644 (file)
 
 /**
  * @ingroup Parser
+ *
+ * @property int $eqpos
+ * @property int $commentEnd
+ * @property int $visualEnd
  */
 class PPDPart {
        /**
index 68f1bb2..b9d796d 100644 (file)
@@ -27,6 +27,8 @@ class PPDStack {
        /** @var PPDStackElement[] */
        public $stack;
        public $rootAccum;
+       /** @var string|array */
+       public $accum;
 
        /**
         * @var PPDStackElement|false
index 116244d..fe2b04e 100644 (file)
@@ -21,6 +21,8 @@
 
 /**
  * @ingroup Parser
+ *
+ * @property int $startPos
  */
 class PPDStackElement {
        /**
index 3f147f0..b50fcfc 100644 (file)
@@ -21,6 +21,9 @@
 
 /**
  * @ingroup Parser
+ *
+ * @property int $depth
+ * @property PPFrame $parent
  */
 interface PPFrame {
        const NO_ARGS = 1;
index ac3a266..a0ec326 100644 (file)
@@ -82,7 +82,7 @@ class PPFrame_DOM implements PPFrame {
         * Create a new child frame
         * $args is optionally a multi-root PPNode or array containing the template arguments
         *
-        * @param bool|array $args
+        * @param bool|array|PPNode_DOM $args
         * @param Title|bool $title
         * @param int $indexOffset
         * @return PPTemplateFrame_DOM
@@ -95,11 +95,12 @@ class PPFrame_DOM implements PPFrame {
                }
                if ( $args !== false ) {
                        $xpath = false;
-                       if ( $args instanceof PPNode ) {
+                       if ( $args instanceof PPNode_DOM ) {
                                $args = $args->node;
                        }
+                       // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
                        foreach ( $args as $arg ) {
-                               if ( $arg instanceof PPNode ) {
+                               if ( $arg instanceof PPNode_DOM ) {
                                        $arg = $arg->node;
                                }
                                if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
@@ -153,7 +154,7 @@ class PPFrame_DOM implements PPFrame {
 
        /**
         * @throws MWException
-        * @param string|PPNode_DOM|DOMNode $root
+        * @param string|PPNode_DOM|DOMNode|DOMNodeList $root
         * @param int $flags
         * @return string
         */
index df740cf..902e4f1 100644 (file)
@@ -40,6 +40,8 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
                $namedArgs = [], $title = false
        ) {
                parent::__construct( $preprocessor );
+               /** @var PPFrame_Hash parent */
+               '@phan-var PPFrame_Hash $parent';
 
                $this->parent = $parent;
                $this->numberedArgs = $numberedArgs;
index 99ca1be..19dd96e 100644 (file)
@@ -31,6 +31,11 @@ abstract class Preprocessor {
 
        const CACHE_VERSION = 1;
 
+       /**
+        * @var Parser
+        */
+       public $parser;
+
        /**
         * @var array Brace matching rules.
         */
index 9f4b7c7..7c372ee 100644 (file)
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
 class Preprocessor_Hash extends Preprocessor {
-
-       /**
-        * @var Parser
-        */
-       public $parser;
-
        const CACHE_PREFIX = 'preprocess-hash';
        const CACHE_VERSION = 2;
 
index 8a82add..66c2bc3 100644 (file)
@@ -84,6 +84,7 @@ class DefaultPreferencesFactory implements PreferencesFactory {
         * @since 1.34
         */
        public static $constructorOptions = [
+               'AllowRequiringEmailForResets',
                'AllowUserCss',
                'AllowUserCssPrefs',
                'AllowUserJs',
@@ -620,6 +621,16 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                                }
                        }
 
+                       if ( $this->options->get( 'AllowRequiringEmailForResets' ) ) {
+                               $defaultPreferences['requireemail'] = [
+                                       'type' => 'toggle',
+                                       'label-message' => 'tog-requireemail',
+                                       'help-message' => 'prefs-help-requireemail',
+                                       'section' => 'personal/email',
+                                       'disabled' => $disableEmailPrefs,
+                               ];
+                       }
+
                        if ( $this->options->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
                                $defaultPreferences['disablemail'] = [
                                        'id' => 'wpAllowEmail',
index 07fe318..c5d4b4a 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 use Composer\Semver\Semver;
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\ScopedCallback;
 use MediaWiki\Shell\Shell;
 use MediaWiki\ShellDisabledError;
@@ -126,15 +127,13 @@ class ExtensionRegistry {
 
                $mtime = $wgExtensionInfoMTime;
                if ( $mtime === false ) {
-                       if ( file_exists( $path ) ) {
-                               $mtime = filemtime( $path );
-                       } else {
-                               throw new Exception( "$path does not exist!" );
-                       }
+                       AtEase::suppressWarnings();
+                       $mtime = filemtime( $path );
+                       AtEase::restoreWarnings();
                        // @codeCoverageIgnoreStart
                        if ( $mtime === false ) {
                                $err = error_get_last();
-                               throw new Exception( "Couldn't stat $path: {$err['message']}" );
+                               throw new Exception( "Unable to open file $path: {$err['message']}" );
                                // @codeCoverageIgnoreEnd
                        }
                }
index 693afcf..6121bbf 100644 (file)
@@ -812,9 +812,9 @@ class ResourceLoader implements LoggerAwareInterface {
                        $errorText = implode( "\n\n", $this->errors );
                        $errorResponse = self::makeComment( $errorText );
                        if ( $context->shouldIncludeScripts() ) {
-                               $errorResponse .= 'if (window.console && console.error) {'
-                                       . Xml::encodeJsCall( 'console.error', [ $errorText ] )
-                                       . "}\n";
+                               $errorResponse .= 'if (window.console && console.error) { console.error('
+                                       . self::encodeJsonForScript( $errorText )
+                                       . "); }\n";
                        }
 
                        // Prepend error info to the response
@@ -1273,8 +1273,7 @@ MESSAGE;
        /**
         * Returns JS code which, when called, will register a given list of messages.
         *
-        * @param mixed $messages Either an associative array mapping message key to value, or a
-        *   JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
+        * @param mixed $messages Associative array mapping message key to value.
         * @return string JavaScript code
         */
        public static function makeMessageSetScript( $messages ) {
@@ -1324,7 +1323,7 @@ MESSAGE;
         * @internal
         * @since 1.32
         * @param mixed $data
-        * @return string JSON
+        * @return string|false JSON string, false on error
         */
        public static function encodeJsonForScript( $data ) {
                // Keep output as small as possible by disabling needless escape modes
@@ -1545,20 +1544,16 @@ MESSAGE;
         * @throws Exception
         */
        public static function makeConfigSetScript( array $configuration ) {
-               $js = Xml::encodeJsCall(
-                       'mw.config.set',
-                       [ $configuration ],
-                       self::inDebugMode()
-               );
-               if ( $js === false ) {
+               $json = self::encodeJsonForScript( $configuration );
+               if ( $json === false ) {
                        $e = new Exception(
                                'JSON serialization of config data failed. ' .
                                'This usually means the config data is not valid UTF-8.'
                        );
                        MWExceptionHandler::logException( $e );
-                       $js = Xml::encodeJsCall( 'mw.log.error', [ $e->__toString() ] );
+                       return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
                }
-               return $js;
+               return "mw.config.set($json);";
        }
 
        /**
index 151b5fd..ea35de6 100644 (file)
@@ -478,7 +478,7 @@ JAVASCRIPT;
                                                        ] );
                                                } else {
                                                        $chunk = ResourceLoader::makeInlineScript(
-                                                               Xml::encodeJsCall( 'mw.loader.load', [ $url ] ),
+                                                               'mw.loader.load(' . ResourceLoader::encodeJsonForScript( $url ) . ');',
                                                                $nonce
                                                        );
                                                }
index d308d50..f2d0856 100644 (file)
@@ -798,9 +798,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
        /**
         * Get a list of file paths for all styles in this module, in order of proper inclusion.
         *
-        * This is considered a private method. Exposed for internal use by WebInstallerOutput.
-        *
-        * @private
+        * @internal Exposed only for use by WebInstallerOutput.
         * @param ResourceLoaderContext $context
         * @return array List of file paths
         */
index 7a7ab89..c0a0921 100644 (file)
@@ -52,16 +52,11 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderFileModule {
         * @return string JavaScript code
         */
        public function getScript( ResourceLoaderContext $context ) {
-               $fileScript = parent::getScript( $context );
-               $langDataScript = Xml::encodeJsCall(
-                       'mw.language.setData',
-                       [
-                               $context->getLanguage(),
-                               $this->getData( $context )
-                       ],
-                       ResourceLoader::inDebugMode()
-               );
-               return $fileScript . $langDataScript;
+               return parent::getScript( $context )
+                       . 'mw.language.setData('
+                       . ResourceLoader::encodeJsonForScript( $context->getLanguage() ) . ','
+                       . ResourceLoader::encodeJsonForScript( $this->getData( $context ) )
+                       . ');';
        }
 
        /**
index d4a34f3..9f583a5 100644 (file)
@@ -121,7 +121,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
                ];
 
-               Hooks::run( 'ResourceLoaderGetConfigVars', [ &$vars, $skin ] );
+               Hooks::run( 'ResourceLoaderGetConfigVars', [ &$vars, $skin, $conf ] );
 
                return $vars;
        }
index b9dc098..61cff82 100644 (file)
@@ -40,10 +40,8 @@ class ResourceLoaderUserDefaultsModule extends ResourceLoaderModule {
         * @return string JavaScript code
         */
        public function getScript( ResourceLoaderContext $context ) {
-               return Xml::encodeJsCall(
-                       'mw.user.options.set',
-                       [ User::getDefaultOptions() ],
-                       ResourceLoader::inDebugMode()
-               );
+               return 'mw.user.options.set('
+                       . ResourceLoader::encodeJsonForScript( User::getDefaultOptions() )
+                       . ');';
        }
 }
index 0d40ad7..ecbb501 100644 (file)
@@ -52,11 +52,12 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
         */
        public function getScript( ResourceLoaderContext $context ) {
                // Use FILTER_NOMIN annotation to prevent needless minification and caching (T84960).
-               return ResourceLoader::FILTER_NOMIN . Xml::encodeJsCall(
-                       'mw.user.options.set',
-                       [ $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS ) ],
-                       ResourceLoader::inDebugMode()
-               );
+               return ResourceLoader::FILTER_NOMIN
+                       . 'mw.user.options.set('
+                       . ResourceLoader::encodeJsonForScript(
+                               $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS )
+                       )
+                       . ');';
        }
 
        /**
index ae4fb67..85c14cb 100644 (file)
@@ -53,11 +53,10 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
         */
        public function getScript( ResourceLoaderContext $context ) {
                // Use FILTER_NOMIN annotation to prevent needless minification and caching (T84960).
-               return ResourceLoader::FILTER_NOMIN . Xml::encodeJsCall(
-                       'mw.user.tokens.set',
-                       [ $this->contextUserTokens( $context ) ],
-                       ResourceLoader::inDebugMode()
-               );
+               return ResourceLoader::FILTER_NOMIN
+                       . 'mw.user.tokens.set('
+                       . ResourceLoader::encodeJsonForScript( $this->contextUserTokens( $context ) )
+                       . ');';
        }
 
        /**
index 74dd7bc..f4ea54f 100644 (file)
@@ -113,8 +113,6 @@ abstract class RevDelList extends RevisionListBase {
         * @since 1.23 Added 'perItemStatus' param
         */
        public function setVisibility( array $params ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $status = Status::newGood();
 
                $bitPars = $params['value'];
@@ -143,7 +141,7 @@ abstract class RevDelList extends RevisionListBase {
                $missing = array_flip( $this->ids );
                $this->clearFileOps();
                $idsForLog = [];
-               $authorIds = $authorIPs = $authorActors = [];
+               $authorActors = [];
 
                if ( $perItemStatus ) {
                        $status->itemStatuses = [];
@@ -225,29 +223,7 @@ abstract class RevDelList extends RevisionListBase {
                                $virtualOldBits |= $removedBits;
 
                                $status->successCount++;
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                                       if ( $item->getAuthorId() > 0 ) {
-                                               $authorIds[] = $item->getAuthorId();
-                                       } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
-                                               $authorIPs[] = $item->getAuthorName();
-                                       }
-                                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                                               $actorId = $item->getAuthorActor();
-                                               // During migration, the actor field might be empty. If so, populate
-                                               // it here.
-                                               if ( !$actorId ) {
-                                                       if ( $item->getAuthorId() > 0 ) {
-                                                               $user = User::newFromId( $item->getAuthorId() );
-                                                       } else {
-                                                               $user = User::newFromName( $item->getAuthorName(), false );
-                                                       }
-                                                       $actorId = $user->getActorId( $dbw );
-                                               }
-                                               $authorActors[] = $actorId;
-                                       }
-                               } elseif ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                                       $authorActors[] = $item->getAuthorActor();
-                               }
+                               $authorActors[] = $item->getAuthorActor();
 
                                // Save the old and new bits in $visibilityChangeMap for
                                // later use.
@@ -291,13 +267,7 @@ abstract class RevDelList extends RevisionListBase {
 
                // Log it
                $authorFields = [];
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                       $authorFields['authorIds'] = $authorIds;
-                       $authorFields['authorIPs'] = $authorIPs;
-               }
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $authorFields['authorActors'] = $authorActors;
-               }
+               $authorFields['authorActors'] = $authorActors;
                $this->updateLog(
                        $logType,
                        [
@@ -363,8 +333,6 @@ abstract class RevDelList extends RevisionListBase {
         *     title:           The target title
         *     ids:             The ID list
         *     comment:         The log comment
-        *     authorIds:       The array of the user IDs of the offenders
-        *     authorIPs:       The array of the IP/anon user offenders
         *     authorActors:    The array of the actor IDs of the offenders
         *     tags:            The array of change tags to apply to the log entry
         * @throws MWException
@@ -387,12 +355,6 @@ abstract class RevDelList extends RevisionListBase {
                $relations = [
                        $field => $params['ids'],
                ];
-               if ( isset( $params['authorIds'] ) ) {
-                       $relations += [
-                               'target_author_id' => $params['authorIds'],
-                               'target_author_ip' => $params['authorIPs'],
-                       ];
-               }
                if ( isset( $params['authorActors'] ) ) {
                        $relations += [
                                'target_author_actor' => $params['authorActors'],
index 5644b95..9a9ab19 100644 (file)
@@ -44,8 +44,6 @@ class RevisionDeleteUser {
         * @return bool True on success, false on failure (e.g. invalid user ID)
         */
        private static function setUsernameBitfields( $name, $userId, $op, IDatabase $dbw = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
                if ( !$userId || ( $op !== '|' && $op !== '&' ) ) {
                        return false; // sanity check
                }
@@ -69,19 +67,26 @@ class RevisionDeleteUser {
                $userTitle = Title::makeTitleSafe( NS_USER, $name );
                $userDbKey = $userTitle->getDBkey();
 
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+               $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
+               if ( $actorId ) {
                        # Hide name from live edits
-                       $dbw->update(
-                               'revision',
-                               [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
-                               [ 'rev_user' => $userId ],
-                               __METHOD__ );
+                       $ids = $dbw->selectFieldValues(
+                               'revision_actor_temp', 'revactor_rev', [ 'revactor_actor' => $actorId ], __METHOD__
+                       );
+                       if ( $ids ) {
+                               $dbw->update(
+                                       'revision',
+                                       [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
+                                       [ 'rev_id' => $ids ],
+                                       __METHOD__
+                               );
+                       }
 
                        # Hide name from deleted edits
                        $dbw->update(
                                'archive',
                                [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
-                               [ 'ar_user_text' => $name ],
+                               [ 'ar_actor' => $actorId ],
                                __METHOD__
                        );
 
@@ -89,7 +94,7 @@ class RevisionDeleteUser {
                        $dbw->update(
                                'logging',
                                [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
-                               [ 'log_user' => $userId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+                               [ 'log_actor' => $actorId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
                                __METHOD__
                        );
 
@@ -97,7 +102,7 @@ class RevisionDeleteUser {
                        $dbw->update(
                                'recentchanges',
                                [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
-                               [ 'rc_user_text' => $name ],
+                               [ 'rc_actor' => $actorId ],
                                __METHOD__
                        );
 
@@ -105,7 +110,7 @@ class RevisionDeleteUser {
                        $dbw->update(
                                'oldimage',
                                [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
-                               [ 'oi_user_text' => $name ],
+                               [ 'oi_actor' => $actorId ],
                                __METHOD__
                        );
 
@@ -113,69 +118,11 @@ class RevisionDeleteUser {
                        $dbw->update(
                                'filearchive',
                                [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
-                               [ 'fa_user_text' => $name ],
+                               [ 'fa_actor' => $actorId ],
                                __METHOD__
                        );
                }
 
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
-                       if ( $actorId ) {
-                               # Hide name from live edits
-                               $ids = $dbw->selectFieldValues(
-                                       'revision_actor_temp', 'revactor_rev', [ 'revactor_actor' => $actorId ], __METHOD__
-                               );
-                               if ( $ids ) {
-                                       $dbw->update(
-                                               'revision',
-                                               [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
-                                               [ 'rev_id' => $ids ],
-                                               __METHOD__
-                                       );
-                               }
-
-                               # Hide name from deleted edits
-                               $dbw->update(
-                                       'archive',
-                                       [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
-                                       [ 'ar_actor' => $actorId ],
-                                       __METHOD__
-                               );
-
-                               # Hide name from logs
-                               $dbw->update(
-                                       'logging',
-                                       [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
-                                       [ 'log_actor' => $actorId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
-                                       __METHOD__
-                               );
-
-                               # Hide name from RC
-                               $dbw->update(
-                                       'recentchanges',
-                                       [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
-                                       [ 'rc_actor' => $actorId ],
-                                       __METHOD__
-                               );
-
-                               # Hide name from live images
-                               $dbw->update(
-                                       'oldimage',
-                                       [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
-                                       [ 'oi_actor' => $actorId ],
-                                       __METHOD__
-                               );
-
-                               # Hide name from deleted images
-                               $dbw->update(
-                                       'filearchive',
-                                       [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
-                                       [ 'fa_actor' => $actorId ],
-                                       __METHOD__
-                               );
-                       }
-               }
-
                # Hide log entries pointing to the user page
                $dbw->update(
                        'logging',
index 09cdf72..a3380ff 100644 (file)
@@ -505,11 +505,10 @@ final class SessionManager implements SessionManagerInterface {
                }
 
                if ( count( $retInfos ) > 1 ) {
-                       $ex = new \OverflowException(
+                       throw new SessionOverflowException(
+                               $retInfos,
                                'Multiple sessions for this request tied for top priority: ' . implode( ', ', $retInfos )
                        );
-                       $ex->sessionInfos = $retInfos;
-                       throw $ex;
                }
 
                return $retInfos ? $retInfos[0] : null;
diff --git a/includes/session/SessionOverflowException.php b/includes/session/SessionOverflowException.php
new file mode 100644 (file)
index 0000000..2a5ed2b
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace MediaWiki\Session;
+
+/**
+ * OverflowException specific to the SessionManager, used when the request had multiple possible
+ * sessions tied for top priority.
+ *
+ * @since 1.34
+ */
+class SessionOverflowException extends \OverflowException {
+       /** @var SessionInfo[] */
+       private $sessionInfos;
+
+       /**
+        * @param SessionInfo[] $sessionInfos Must have at least two elements
+        * @param string $msg
+        * @throws \InvalidArgumentException If $sessionInfos has less than 2 elements
+        */
+       function __construct( array $sessionInfos, $msg ) {
+               if ( count( $sessionInfos ) < 2 ) {
+                       throw new \InvalidArgumentException( 'Expected at least two SessionInfo objects.' );
+               }
+               parent::__construct( $msg );
+               $this->sessionInfos = $sessionInfos;
+       }
+
+       /**
+        * @return SessionInfo[]
+        */
+       public function getSessionInfos() : array {
+               return $this->sessionInfos;
+       }
+}
index ce80c1a..e5a28d9 100644 (file)
@@ -95,9 +95,8 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
 
        /**
         * Load basic request parameters for this Special page.
-        * @param string $subPage
         */
-       private function loadRequestParameters( $subPage ) {
+       private function loadRequestParameters() {
                if ( $this->mLoadedRequest ) {
                        return;
                }
@@ -105,7 +104,6 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                $request = $this->getRequest();
 
                $this->mPosted = $request->wasPosted();
-               $this->mIsReturn = $subPage === 'return';
                $this->mAction = $request->getVal( 'action' );
                $this->mFromHTTP = $request->getBool( 'fromhttp', false )
                        || $request->getBool( 'wpFromhttp', false );
@@ -124,7 +122,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
        protected function load( $subPage ) {
                global $wgSecureLogin;
 
-               $this->loadRequestParameters( $subPage );
+               $this->loadRequestParameters();
                if ( $this->mLoaded ) {
                        return;
                }
@@ -203,7 +201,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
 
        protected function beforeExecute( $subPage ) {
                // finish initializing the class before processing the request - T135924
-               $this->loadRequestParameters( $subPage );
+               $this->loadRequestParameters();
                return parent::beforeExecute( $subPage );
        }
 
index 6cc6e4e..b7eb3c0 100644 (file)
@@ -77,40 +77,40 @@ abstract class QueryPage extends SpecialPage {
                if ( $qp === null ) {
                        // QueryPage subclass, Special page name
                        $qp = [
-                               [ AncientPagesPage::class, 'Ancientpages' ],
-                               [ BrokenRedirectsPage::class, 'BrokenRedirects' ],
-                               [ DeadendPagesPage::class, 'Deadendpages' ],
-                               [ DoubleRedirectsPage::class, 'DoubleRedirects' ],
-                               [ FileDuplicateSearchPage::class, 'FileDuplicateSearch' ],
-                               [ ListDuplicatedFilesPage::class, 'ListDuplicatedFiles' ],
-                               [ LinkSearchPage::class, 'LinkSearch' ],
-                               [ ListredirectsPage::class, 'Listredirects' ],
-                               [ LonelyPagesPage::class, 'Lonelypages' ],
-                               [ LongPagesPage::class, 'Longpages' ],
-                               [ MediaStatisticsPage::class, 'MediaStatistics' ],
-                               [ MIMEsearchPage::class, 'MIMEsearch' ],
-                               [ MostcategoriesPage::class, 'Mostcategories' ],
+                               [ SpecialAncientPages::class, 'Ancientpages' ],
+                               [ SpecialBrokenRedirects::class, 'BrokenRedirects' ],
+                               [ SpecialDeadendPages::class, 'Deadendpages' ],
+                               [ SpecialDoubleRedirects::class, 'DoubleRedirects' ],
+                               [ SpecialFileDuplicateSearch::class, 'FileDuplicateSearch' ],
+                               [ SpecialListDuplicatedFiles::class, 'ListDuplicatedFiles' ],
+                               [ SpecialLinkSearch::class, 'LinkSearch' ],
+                               [ SpecialListRedirects::class, 'Listredirects' ],
+                               [ SpecialLonelyPages::class, 'Lonelypages' ],
+                               [ SpecialLongPages::class, 'Longpages' ],
+                               [ SpecialMediaStatistics::class, 'MediaStatistics' ],
+                               [ SpecialMIMESearch::class, 'MIMEsearch' ],
+                               [ SpecialMostCategories::class, 'Mostcategories' ],
                                [ MostimagesPage::class, 'Mostimages' ],
-                               [ MostinterwikisPage::class, 'Mostinterwikis' ],
-                               [ MostlinkedCategoriesPage::class, 'Mostlinkedcategories' ],
-                               [ MostlinkedTemplatesPage::class, 'Mostlinkedtemplates' ],
-                               [ MostlinkedPage::class, 'Mostlinked' ],
-                               [ MostrevisionsPage::class, 'Mostrevisions' ],
-                               [ FewestrevisionsPage::class, 'Fewestrevisions' ],
-                               [ ShortPagesPage::class, 'Shortpages' ],
-                               [ UncategorizedCategoriesPage::class, 'Uncategorizedcategories' ],
-                               [ UncategorizedPagesPage::class, 'Uncategorizedpages' ],
-                               [ UncategorizedImagesPage::class, 'Uncategorizedimages' ],
-                               [ UncategorizedTemplatesPage::class, 'Uncategorizedtemplates' ],
-                               [ UnusedCategoriesPage::class, 'Unusedcategories' ],
-                               [ UnusedimagesPage::class, 'Unusedimages' ],
-                               [ WantedCategoriesPage::class, 'Wantedcategories' ],
+                               [ SpecialMostInterwikis::class, 'Mostinterwikis' ],
+                               [ SpecialMostLinkedCategories::class, 'Mostlinkedcategories' ],
+                               [ SpecialMostLinkedTemplates::class, 'Mostlinkedtemplates' ],
+                               [ SpecialMostLinked::class, 'Mostlinked' ],
+                               [ SpecialMostRevisions::class, 'Mostrevisions' ],
+                               [ SpecialFewestRevisions::class, 'Fewestrevisions' ],
+                               [ SpecialShortPages::class, 'Shortpages' ],
+                               [ SpecialUncategorizedCategories::class, 'Uncategorizedcategories' ],
+                               [ SpecialUncategorizedPages::class, 'Uncategorizedpages' ],
+                               [ SpecialUncategorizedImages::class, 'Uncategorizedimages' ],
+                               [ SpecialUncategorizedTemplates::class, 'Uncategorizedtemplates' ],
+                               [ SpecialUnusedCategories::class, 'Unusedcategories' ],
+                               [ SpecialUnusedImages::class, 'Unusedimages' ],
+                               [ SpecialWantedCategories::class, 'Wantedcategories' ],
                                [ WantedFilesPage::class, 'Wantedfiles' ],
                                [ WantedPagesPage::class, 'Wantedpages' ],
-                               [ WantedTemplatesPage::class, 'Wantedtemplates' ],
-                               [ UnwatchedpagesPage::class, 'Unwatchedpages' ],
-                               [ UnusedtemplatesPage::class, 'Unusedtemplates' ],
-                               [ WithoutInterwikiPage::class, 'Withoutinterwiki' ],
+                               [ SpecialWantedTemplates::class, 'Wantedtemplates' ],
+                               [ SpecialUnwatchedPages::class, 'Unwatchedpages' ],
+                               [ SpecialUnusedTemplates::class, 'Unusedtemplates' ],
+                               [ SpecialWithoutInterwiki::class, 'Withoutinterwiki' ],
                        ];
                        Hooks::run( 'wgQueryPages', [ &$qp ] );
                }
index 8134c9a..5ac5f82 100644 (file)
@@ -34,6 +34,7 @@ use RequestContext;
 use SpecialPage;
 use Title;
 use User;
+use Wikimedia\ObjectFactory;
 
 /**
  * Factory for handling the special page list and generating SpecialPage objects.
@@ -68,35 +69,35 @@ class SpecialPageFactory {
         */
        private static $coreList = [
                // Maintenance Reports
-               'BrokenRedirects' => \BrokenRedirectsPage::class,
-               'Deadendpages' => \DeadendPagesPage::class,
-               'DoubleRedirects' => \DoubleRedirectsPage::class,
-               'Longpages' => \LongPagesPage::class,
-               'Ancientpages' => \AncientPagesPage::class,
-               'Lonelypages' => \LonelyPagesPage::class,
-               'Fewestrevisions' => \FewestrevisionsPage::class,
-               'Withoutinterwiki' => \WithoutInterwikiPage::class,
+               'BrokenRedirects' => \SpecialBrokenRedirects::class,
+               'Deadendpages' => \SpecialDeadendPages::class,
+               'DoubleRedirects' => \SpecialDoubleRedirects::class,
+               'Longpages' => \SpecialLongPages::class,
+               'Ancientpages' => \SpecialAncientPages::class,
+               'Lonelypages' => \SpecialLonelyPages::class,
+               'Fewestrevisions' => \SpecialFewestRevisions::class,
+               'Withoutinterwiki' => \SpecialWithoutInterwiki::class,
                'Protectedpages' => \SpecialProtectedpages::class,
                'Protectedtitles' => \SpecialProtectedtitles::class,
-               'Shortpages' => \ShortPagesPage::class,
-               'Uncategorizedcategories' => \UncategorizedCategoriesPage::class,
-               'Uncategorizedimages' => \UncategorizedImagesPage::class,
-               'Uncategorizedpages' => \UncategorizedPagesPage::class,
-               'Uncategorizedtemplates' => \UncategorizedTemplatesPage::class,
-               'Unusedcategories' => \UnusedCategoriesPage::class,
-               'Unusedimages' => \UnusedimagesPage::class,
-               'Unusedtemplates' => \UnusedtemplatesPage::class,
-               'Unwatchedpages' => \UnwatchedpagesPage::class,
-               'Wantedcategories' => \WantedCategoriesPage::class,
+               'Shortpages' => \SpecialShortPages::class,
+               'Uncategorizedcategories' => \SpecialUncategorizedCategories::class,
+               'Uncategorizedimages' => \SpecialUncategorizedImages::class,
+               'Uncategorizedpages' => \SpecialUncategorizedPages::class,
+               'Uncategorizedtemplates' => \SpecialUncategorizedTemplates::class,
+               'Unusedcategories' => \SpecialUnusedCategories::class,
+               'Unusedimages' => \SpecialUnusedImages::class,
+               'Unusedtemplates' => \SpecialUnusedTemplates::class,
+               'Unwatchedpages' => \SpecialUnwatchedPages::class,
+               'Wantedcategories' => \SpecialWantedCategories::class,
                'Wantedfiles' => \WantedFilesPage::class,
                'Wantedpages' => \WantedPagesPage::class,
-               'Wantedtemplates' => \WantedTemplatesPage::class,
+               'Wantedtemplates' => \SpecialWantedTemplates::class,
 
                // List of pages
                'Allpages' => \SpecialAllPages::class,
                'Prefixindex' => \SpecialPrefixindex::class,
                'Categories' => \SpecialCategories::class,
-               'Listredirects' => \ListredirectsPage::class,
+               'Listredirects' => \SpecialListRedirects::class,
                'PagesWithProp' => \SpecialPagesWithProp::class,
                'TrackingCategories' => \SpecialTrackingCategories::class,
 
@@ -118,7 +119,7 @@ class SpecialPageFactory {
                'ChangePassword' => \SpecialChangePassword::class,
                'BotPasswords' => \SpecialBotPasswords::class,
                'PasswordReset' => \SpecialPasswordReset::class,
-               'DeletedContributions' => \DeletedContributionsPage::class,
+               'DeletedContributions' => \SpecialDeletedContributions::class,
                'Preferences' => \SpecialPreferences::class,
                'ResetTokens' => \SpecialResetTokens::class,
                'Contributions' => \SpecialContributions::class,
@@ -143,12 +144,12 @@ class SpecialPageFactory {
                // Media reports and uploads
                'Listfiles' => \SpecialListFiles::class,
                'Filepath' => \SpecialFilepath::class,
-               'MediaStatistics' => \MediaStatisticsPage::class,
-               'MIMEsearch' => \MIMEsearchPage::class,
-               'FileDuplicateSearch' => \FileDuplicateSearchPage::class,
+               'MediaStatistics' => \SpecialMediaStatistics::class,
+               'MIMEsearch' => \SpecialMIMESearch::class,
+               'FileDuplicateSearch' => \SpecialFileDuplicateSearch::class,
                'Upload' => \SpecialUpload::class,
                'UploadStash' => \SpecialUploadStash::class,
-               'ListDuplicatedFiles' => \ListDuplicatedFilesPage::class,
+               'ListDuplicatedFiles' => \SpecialListDuplicatedFiles::class,
 
                // Data and tools
                'ApiSandbox' => \SpecialApiSandbox::class,
@@ -159,7 +160,7 @@ class SpecialPageFactory {
                'Unlockdb' => \SpecialUnlockdb::class,
 
                // Redirecting special pages
-               'LinkSearch' => \LinkSearchPage::class,
+               'LinkSearch' => \SpecialLinkSearch::class,
                'Randompage' => \RandomPage::class,
                'RandomInCategory' => \SpecialRandomInCategory::class,
                'Randomredirect' => \SpecialRandomredirect::class,
@@ -167,13 +168,13 @@ class SpecialPageFactory {
                'GoToInterwiki' => \SpecialGoToInterwiki::class,
 
                // High use pages
-               'Mostlinkedcategories' => \MostlinkedCategoriesPage::class,
+               'Mostlinkedcategories' => \SpecialMostLinkedCategories::class,
                'Mostimages' => \MostimagesPage::class,
-               'Mostinterwikis' => \MostinterwikisPage::class,
-               'Mostlinked' => \MostlinkedPage::class,
-               'Mostlinkedtemplates' => \MostlinkedTemplatesPage::class,
-               'Mostcategories' => \MostcategoriesPage::class,
-               'Mostrevisions' => \MostrevisionsPage::class,
+               'Mostinterwikis' => \SpecialMostInterwikis::class,
+               'Mostlinked' => \SpecialMostLinked::class,
+               'Mostlinkedtemplates' => \SpecialMostLinkedTemplates::class,
+               'Mostcategories' => \SpecialMostCategories::class,
+               'Mostrevisions' => \SpecialMostRevisions::class,
 
                // Page tools
                'ComparePages' => \SpecialComparePages::class,
@@ -191,7 +192,12 @@ class SpecialPageFactory {
                'ApiHelp' => \SpecialApiHelp::class,
                'Blankpage' => \SpecialBlankpage::class,
                'Diff' => \SpecialDiff::class,
-               'EditTags' => \SpecialEditTags::class,
+               'EditTags' => [
+                       'class' => \SpecialEditTags::class,
+                       'services' => [
+                               'PermissionManager',
+                       ],
+               ],
                'Emailuser' => \SpecialEmailUser::class,
                'Movepage' => \MovePageForm::class,
                'Mycontributions' => \SpecialMycontributions::class,
@@ -221,6 +227,9 @@ class SpecialPageFactory {
        /** @var Language */
        private $contLang;
 
+       /** @var ObjectFactory */
+       private $objectFactory;
+
        /**
         * TODO Make this a const when HHVM support is dropped (T192166)
         *
@@ -241,11 +250,17 @@ class SpecialPageFactory {
        /**
         * @param ServiceOptions $options
         * @param Language $contLang
+        * @param ObjectFactory $objectFactory
         */
-       public function __construct( ServiceOptions $options, Language $contLang ) {
+       public function __construct(
+               ServiceOptions $options,
+               Language $contLang,
+               ObjectFactory $objectFactory
+       ) {
                $options->assertRequiredOptions( self::$constructorOptions );
                $this->options = $options;
                $this->contLang = $contLang;
+               $this->objectFactory = $objectFactory;
        }
 
        /**
@@ -272,8 +287,8 @@ class SpecialPageFactory {
                        }
 
                        if ( $this->options->get( 'EmailAuthentication' ) ) {
-                               $this->list['Confirmemail'] = \EmailConfirmation::class;
-                               $this->list['Invalidateemail'] = \EmailInvalidation::class;
+                               $this->list['Confirmemail'] = \SpecialConfirmEmail::class;
+                               $this->list['Invalidateemail'] = \SpecialEmailInvalidate::class;
                        }
 
                        if ( $this->options->get( 'EnableEmail' ) ) {
@@ -412,14 +427,22 @@ class SpecialPageFactory {
                if ( isset( $specialPageList[$realName] ) ) {
                        $rec = $specialPageList[$realName];
 
-                       if ( is_callable( $rec ) ) {
-                               // Use callback to instantiate the special page
-                               $page = $rec();
-                       } elseif ( is_string( $rec ) ) {
-                               $className = $rec;
-                               $page = new $className;
-                       } elseif ( $rec instanceof SpecialPage ) {
+                       if ( $rec instanceof SpecialPage ) {
+                               wfDeprecated(
+                                       "a SpecialPage instance (for $realName) in " .
+                                       '$wgSpecialPages or from the SpecialPage_initList hook',
+                                       '1.34'
+                               );
+
                                $page = $rec; // XXX: we should deep clone here
+                       } elseif ( is_array( $rec ) || is_string( $rec ) || is_callable( $rec ) ) {
+                               $page = $this->objectFactory->createObject(
+                                       $rec,
+                                       [
+                                               'allowClassName' => true,
+                                               'allowCallable' => true
+                                       ]
+                               );
                        } else {
                                $page = null;
                        }
diff --git a/includes/specials/SpecialAncientPages.php b/includes/specials/SpecialAncientPages.php
new file mode 100644 (file)
index 0000000..a17c121
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Implements Special:Ancientpages
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Implements Special:Ancientpages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialAncientPages extends QueryPage {
+
+       function __construct( $name = 'Ancientpages' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               $tables = [ 'page', 'revision' ];
+               $conds = [
+                       'page_namespace' =>
+                               MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
+                       'page_is_redirect' => 0
+               ];
+               $joinConds = [
+                       'revision' => [
+                               'JOIN', [
+                                       'page_latest = rev_id'
+                               ]
+                       ],
+               ];
+
+               // Allow extensions to modify the query
+               Hooks::run( 'AncientPagesQuery', [ &$tables, &$conds, &$joinConds ] );
+
+               return [
+                       'tables' => $tables,
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'rev_timestamp'
+                       ],
+                       'conds' => $conds,
+                       'join_conds' => $joinConds
+               ];
+       }
+
+       public function usesTimestamps() {
+               return true;
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       public function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $d = $this->getLanguage()->userTimeAndDate( $result->value, $this->getUser() );
+               $title = Title::makeTitle( $result->namespace, $result->title );
+               $linkRenderer = $this->getLinkRenderer();
+               $link = $linkRenderer->makeKnownLink(
+                       $title,
+                       new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()->
+                               convert( htmlspecialchars( $title->getPrefixedText() ) ) )
+               );
+
+               return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
deleted file mode 100644 (file)
index a32393e..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-/**
- * Implements Special:Ancientpages
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Implements Special:Ancientpages
- *
- * @ingroup SpecialPage
- */
-class AncientPagesPage extends QueryPage {
-
-       function __construct( $name = 'Ancientpages' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               $tables = [ 'page', 'revision' ];
-               $conds = [
-                       'page_namespace' =>
-                               MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
-                       'page_is_redirect' => 0
-               ];
-               $joinConds = [
-                       'revision' => [
-                               'JOIN', [
-                                       'page_latest = rev_id'
-                               ]
-                       ],
-               ];
-
-               // Allow extensions to modify the query
-               Hooks::run( 'AncientPagesQuery', [ &$tables, &$conds, &$joinConds ] );
-
-               return [
-                       'tables' => $tables,
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'rev_timestamp'
-                       ],
-                       'conds' => $conds,
-                       'join_conds' => $joinConds
-               ];
-       }
-
-       public function usesTimestamps() {
-               return true;
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       public function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $d = $this->getLanguage()->userTimeAndDate( $result->value, $this->getUser() );
-               $title = Title::makeTitle( $result->namespace, $result->title );
-               $linkRenderer = $this->getLinkRenderer();
-               $link = $linkRenderer->makeKnownLink(
-                       $title,
-                       new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()->
-                               convert( htmlspecialchars( $title->getPrefixedText() ) ) )
-               );
-
-               return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
index b3d2358..6385359 100644 (file)
@@ -82,8 +82,9 @@ class SpecialBlockList extends SpecialPage {
                        'Options' => [
                                'type' => 'multiselect',
                                'options-messages' => [
-                                       'blocklist-userblocks' => 'userblocks',
                                        'blocklist-tempblocks' => 'tempblocks',
+                                       'blocklist-indefblocks' => 'indefblocks',
+                                       'blocklist-userblocks' => 'userblocks',
                                        'blocklist-addressblocks' => 'addressblocks',
                                        'blocklist-rangeblocks' => 'rangeblocks',
                                ],
@@ -136,6 +137,7 @@ class SpecialBlockList extends SpecialPage {
         */
        protected function getBlockListPager() {
                $conds = [];
+               $db = $this->getDB();
                # Is the user allowed to see hidden blocks?
                if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
                        $conds['ipb_deleted'] = 0;
@@ -153,7 +155,7 @@ class SpecialBlockList extends SpecialPage {
                                case DatabaseBlock::TYPE_IP:
                                case DatabaseBlock::TYPE_RANGE:
                                        list( $start, $end ) = IP::parseRange( $target );
-                                       $conds[] = wfGetDB( DB_REPLICA )->makeList(
+                                       $conds[] = $db->makeList(
                                                [
                                                        'ipb_address' => $target,
                                                        DatabaseBlock::getRangeCond( $start, $end )
@@ -174,9 +176,6 @@ class SpecialBlockList extends SpecialPage {
                if ( in_array( 'userblocks', $this->options ) ) {
                        $conds['ipb_user'] = 0;
                }
-               if ( in_array( 'tempblocks', $this->options ) ) {
-                       $conds['ipb_expiry'] = 'infinity';
-               }
                if ( in_array( 'addressblocks', $this->options ) ) {
                        $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start";
                }
@@ -184,10 +183,21 @@ class SpecialBlockList extends SpecialPage {
                        $conds[] = "ipb_range_end = ipb_range_start";
                }
 
+               $hideTemp = in_array( 'tempblocks', $this->options );
+               $hideIndef = in_array( 'indefblocks', $this->options );
+               if ( $hideTemp && $hideIndef ) {
+                       // If both types are hidden, ensure query doesn't produce any results
+                       $conds[] = '1=0';
+               } elseif ( $hideTemp ) {
+                       $conds['ipb_expiry'] = $db->getInfinity();
+               } elseif ( $hideIndef ) {
+                       $conds[] = "ipb_expiry != " . $db->addQuotes( $db->getInfinity() );
+               }
+
                if ( $this->blockType === 'sitewide' ) {
-                       $conds[] = 'ipb_sitewide = 1';
+                       $conds['ipb_sitewide'] = 1;
                } elseif ( $this->blockType === 'partial' ) {
-                       $conds[] = 'ipb_sitewide = 0';
+                       $conds['ipb_sitewide'] = 0;
                }
 
                return new BlockListPager( $this, $conds );
@@ -243,4 +253,13 @@ class SpecialBlockList extends SpecialPage {
        protected function getGroupName() {
                return 'users';
        }
+
+       /**
+        * Return a IDatabase object for reading
+        *
+        * @return IDatabase
+        */
+       protected function getDB() {
+               return wfGetDB( DB_REPLICA );
+       }
 }
index 17f89f9..9431cef 100644 (file)
@@ -30,7 +30,7 @@ use Wikimedia\Rdbms\IDatabase;
  *
  * @ingroup SpecialPage
  */
-class BrokenRedirectsPage extends QueryPage {
+class SpecialBrokenRedirects extends QueryPage {
        function __construct( $name = 'BrokenRedirects' ) {
                parent::__construct( $name );
        }
diff --git a/includes/specials/SpecialConfirmEmail.php b/includes/specials/SpecialConfirmEmail.php
new file mode 100644 (file)
index 0000000..f86a133
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+/**
+ * Implements Special:Confirmemail
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * Special page allows users to request email confirmation message, and handles
+ * processing of the confirmation code when the link in the email is followed
+ *
+ * @ingroup SpecialPage
+ * @author Brion Vibber
+ * @author Rob Church <robchur@gmail.com>
+ */
+class SpecialConfirmEmail extends UnlistedSpecialPage {
+       public function __construct() {
+               parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       /**
+        * Main execution point
+        *
+        * @param null|string $code Confirmation code passed to the page
+        * @throws PermissionsError
+        * @throws ReadOnlyError
+        * @throws UserNotLoggedIn
+        */
+       function execute( $code ) {
+               // Ignore things like master queries/connections on GET requests.
+               // It's very convenient to just allow formless link usage.
+               $trxProfiler = Profiler::instance()->getTransactionProfiler();
+
+               $this->setHeaders();
+               $this->checkReadOnly();
+               $this->checkPermissions();
+
+               // This could also let someone check the current email address, so
+               // require both permissions.
+               if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
+                       throw new PermissionsError( 'viewmyprivateinfo' );
+               }
+
+               if ( $code === null || $code === '' ) {
+                       $this->requireLogin( 'confirmemail_needlogin' );
+                       if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
+                               $this->showRequestForm();
+                       } else {
+                               $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
+                       }
+               } else {
+                       $old = $trxProfiler->setSilenced( true );
+                       $this->attemptConfirm( $code );
+                       $trxProfiler->setSilenced( $old );
+               }
+       }
+
+       /**
+        * Show a nice form for the user to request a confirmation mail
+        */
+       function showRequestForm() {
+               $user = $this->getUser();
+               $out = $this->getOutput();
+
+               if ( !$user->isEmailConfirmed() ) {
+                       $descriptor = [];
+                       if ( $user->isEmailConfirmationPending() ) {
+                               $descriptor += [
+                                       'pending' => [
+                                               'type' => 'info',
+                                               'raw' => true,
+                                               'default' => "<div class=\"error mw-confirmemail-pending\">\n" .
+                                                       $this->msg( 'confirmemail_pending' )->escaped() .
+                                                       "\n</div>",
+                                       ],
+                               ];
+                       }
+
+                       $out->addWikiMsg( 'confirmemail_text' );
+                       $form = HTMLForm::factory( 'ooui', $descriptor, $this->getContext() );
+                       $form
+                               ->setMethod( 'post' )
+                               ->setAction( $this->getPageTitle()->getLocalURL() )
+                               ->setSubmitTextMsg( 'confirmemail_send' )
+                               ->setSubmitCallback( [ $this, 'submitSend' ] );
+
+                       $retval = $form->show();
+
+                       if ( $retval === true ) {
+                               // should never happen, but if so, don't let the user without any message
+                               $out->addWikiMsg( 'confirmemail_sent' );
+                       } elseif ( $retval instanceof Status && $retval->isGood() ) {
+                               $out->addWikiTextAsInterface( $retval->getValue() );
+                       }
+               } else {
+                       // date and time are separate parameters to facilitate localisation.
+                       // $time is kept for backward compat reasons.
+                       // 'emailauthenticated' is also used in SpecialPreferences.php
+                       $lang = $this->getLanguage();
+                       $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
+                       $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
+                       $d = $lang->userDate( $emailAuthenticated, $user );
+                       $t = $lang->userTime( $emailAuthenticated, $user );
+                       $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
+               }
+       }
+
+       /**
+        * Callback for HTMLForm send confirmation mail.
+        *
+        * @return Status Status object with the result
+        */
+       public function submitSend() {
+               $status = $this->getUser()->sendConfirmationMail();
+               if ( $status->isGood() ) {
+                       return Status::newGood( $this->msg( 'confirmemail_sent' )->text() );
+               } else {
+                       return Status::newFatal( new RawMessage(
+                               $status->getWikiText( 'confirmemail_sendfailed' )
+                       ) );
+               }
+       }
+
+       /**
+        * Attempt to confirm the user's email address and show success or failure
+        * as needed; if successful, take the user to log in
+        *
+        * @param string $code Confirmation code
+        */
+       private function attemptConfirm( $code ) {
+               $user = User::newFromConfirmationCode( $code, User::READ_EXCLUSIVE );
+               if ( !is_object( $user ) ) {
+                       $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
+
+                       return;
+               }
+
+               // rate limit email confirmations
+               if ( $user->pingLimiter( 'confirmemail' ) ) {
+                       $this->getOutput()->addWikiMsg( 'actionthrottledtext' );
+
+                       return;
+               }
+
+               $user->confirmEmail();
+               $user->saveSettings();
+               $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
+               $this->getOutput()->addWikiMsg( $message );
+
+               if ( !$this->getUser()->isLoggedIn() ) {
+                       $title = SpecialPage::getTitleFor( 'Userlogin' );
+                       $this->getOutput()->returnToMain( true, $title );
+               }
+       }
+}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
deleted file mode 100644 (file)
index 7f32719..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-/**
- * Implements Special:Confirmemail
- *
- * 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 SpecialPage
- */
-
-/**
- * Special page allows users to request email confirmation message, and handles
- * processing of the confirmation code when the link in the email is followed
- *
- * @ingroup SpecialPage
- * @author Brion Vibber
- * @author Rob Church <robchur@gmail.com>
- */
-class EmailConfirmation extends UnlistedSpecialPage {
-       public function __construct() {
-               parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
-       }
-
-       public function doesWrites() {
-               return true;
-       }
-
-       /**
-        * Main execution point
-        *
-        * @param null|string $code Confirmation code passed to the page
-        * @throws PermissionsError
-        * @throws ReadOnlyError
-        * @throws UserNotLoggedIn
-        */
-       function execute( $code ) {
-               // Ignore things like master queries/connections on GET requests.
-               // It's very convenient to just allow formless link usage.
-               $trxProfiler = Profiler::instance()->getTransactionProfiler();
-
-               $this->setHeaders();
-               $this->checkReadOnly();
-               $this->checkPermissions();
-
-               // This could also let someone check the current email address, so
-               // require both permissions.
-               if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
-                       throw new PermissionsError( 'viewmyprivateinfo' );
-               }
-
-               if ( $code === null || $code === '' ) {
-                       $this->requireLogin( 'confirmemail_needlogin' );
-                       if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
-                               $this->showRequestForm();
-                       } else {
-                               $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
-                       }
-               } else {
-                       $old = $trxProfiler->setSilenced( true );
-                       $this->attemptConfirm( $code );
-                       $trxProfiler->setSilenced( $old );
-               }
-       }
-
-       /**
-        * Show a nice form for the user to request a confirmation mail
-        */
-       function showRequestForm() {
-               $user = $this->getUser();
-               $out = $this->getOutput();
-
-               if ( !$user->isEmailConfirmed() ) {
-                       $descriptor = [];
-                       if ( $user->isEmailConfirmationPending() ) {
-                               $descriptor += [
-                                       'pending' => [
-                                               'type' => 'info',
-                                               'raw' => true,
-                                               'default' => "<div class=\"error mw-confirmemail-pending\">\n" .
-                                                       $this->msg( 'confirmemail_pending' )->escaped() .
-                                                       "\n</div>",
-                                       ],
-                               ];
-                       }
-
-                       $out->addWikiMsg( 'confirmemail_text' );
-                       $form = HTMLForm::factory( 'ooui', $descriptor, $this->getContext() );
-                       $form
-                               ->setMethod( 'post' )
-                               ->setAction( $this->getPageTitle()->getLocalURL() )
-                               ->setSubmitTextMsg( 'confirmemail_send' )
-                               ->setSubmitCallback( [ $this, 'submitSend' ] );
-
-                       $retval = $form->show();
-
-                       if ( $retval === true ) {
-                               // should never happen, but if so, don't let the user without any message
-                               $out->addWikiMsg( 'confirmemail_sent' );
-                       } elseif ( $retval instanceof Status && $retval->isGood() ) {
-                               $out->addWikiTextAsInterface( $retval->getValue() );
-                       }
-               } else {
-                       // date and time are separate parameters to facilitate localisation.
-                       // $time is kept for backward compat reasons.
-                       // 'emailauthenticated' is also used in SpecialPreferences.php
-                       $lang = $this->getLanguage();
-                       $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
-                       $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
-                       $d = $lang->userDate( $emailAuthenticated, $user );
-                       $t = $lang->userTime( $emailAuthenticated, $user );
-                       $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
-               }
-       }
-
-       /**
-        * Callback for HTMLForm send confirmation mail.
-        *
-        * @return Status Status object with the result
-        */
-       public function submitSend() {
-               $status = $this->getUser()->sendConfirmationMail();
-               if ( $status->isGood() ) {
-                       return Status::newGood( $this->msg( 'confirmemail_sent' )->text() );
-               } else {
-                       return Status::newFatal( new RawMessage(
-                               $status->getWikiText( 'confirmemail_sendfailed' )
-                       ) );
-               }
-       }
-
-       /**
-        * Attempt to confirm the user's email address and show success or failure
-        * as needed; if successful, take the user to log in
-        *
-        * @param string $code Confirmation code
-        */
-       private function attemptConfirm( $code ) {
-               $user = User::newFromConfirmationCode( $code, User::READ_EXCLUSIVE );
-               if ( !is_object( $user ) ) {
-                       $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
-
-                       return;
-               }
-
-               // rate limit email confirmations
-               if ( $user->pingLimiter( 'confirmemail' ) ) {
-                       $this->getOutput()->addWikiMsg( 'actionthrottledtext' );
-
-                       return;
-               }
-
-               $user->confirmEmail();
-               $user->saveSettings();
-               $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
-               $this->getOutput()->addWikiMsg( $message );
-
-               if ( !$this->getUser()->isLoggedIn() ) {
-                       $title = SpecialPage::getTitleFor( 'Userlogin' );
-                       $this->getOutput()->returnToMain( true, $title );
-               }
-       }
-}
index 4599b22..40c0edf 100644 (file)
@@ -554,13 +554,9 @@ class SpecialContributions extends IncludableSpecialPage {
                        $filterSelection = Html::rawElement( 'div', [], '' );
                }
 
-               $labelUsername = Xml::radioLabel(
+               $labelUsername = Xml::label(
                        $this->msg( 'sp-contributions-username' )->text(),
-                       'contribs',
-                       'user',
-                       'user',
-                       true,
-                       [ 'class' => 'mw-input' ]
+                       'mw-target-user-or-ip'
                );
                $input = Html::input(
                        'target',
diff --git a/includes/specials/SpecialDeadendPages.php b/includes/specials/SpecialDeadendPages.php
new file mode 100644 (file)
index 0000000..9159442
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Implements Special:Deadenpages
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A special page that list pages that contain no link to other pages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialDeadendPages extends PageQueryPage {
+
+       function __construct( $name = 'Deadendpages' ) {
+               parent::__construct( $name );
+       }
+
+       function getPageHeader() {
+               return $this->msg( 'deadendpagestext' )->parseAsBlock();
+       }
+
+       /**
+        * LEFT JOIN is expensive
+        *
+        * @return bool
+        */
+       function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       /**
+        * @return bool
+        */
+       function sortDescending() {
+               return false;
+       }
+
+       function getQueryInfo() {
+               return [
+                       'tables' => [ 'page', 'pagelinks' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title'
+                       ],
+                       'conds' => [
+                               'pl_from IS NULL',
+                               'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+                                       getContentNamespaces(),
+                               'page_is_redirect' => 0
+                       ],
+                       'join_conds' => [
+                               'pagelinks' => [
+                                       'LEFT JOIN',
+                                       [ 'page_id=pl_from' ]
+                               ]
+                       ]
+               ];
+       }
+
+       function getOrderFields() {
+               // For some crazy reason ordering by a constant
+               // causes a filesort
+               if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
+                       getContentNamespaces() ) > 1
+               ) {
+                       return [ 'page_namespace', 'page_title' ];
+               } else {
+                       return [ 'page_title' ];
+               }
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php
deleted file mode 100644 (file)
index 2a967c5..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-/**
- * Implements Special:Deadenpages
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A special page that list pages that contain no link to other pages
- *
- * @ingroup SpecialPage
- */
-class DeadendPagesPage extends PageQueryPage {
-
-       function __construct( $name = 'Deadendpages' ) {
-               parent::__construct( $name );
-       }
-
-       function getPageHeader() {
-               return $this->msg( 'deadendpagestext' )->parseAsBlock();
-       }
-
-       /**
-        * LEFT JOIN is expensive
-        *
-        * @return bool
-        */
-       function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       /**
-        * @return bool
-        */
-       function sortDescending() {
-               return false;
-       }
-
-       function getQueryInfo() {
-               return [
-                       'tables' => [ 'page', 'pagelinks' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title'
-                       ],
-                       'conds' => [
-                               'pl_from IS NULL',
-                               'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
-                                       getContentNamespaces(),
-                               'page_is_redirect' => 0
-                       ],
-                       'join_conds' => [
-                               'pagelinks' => [
-                                       'LEFT JOIN',
-                                       [ 'page_id=pl_from' ]
-                               ]
-                       ]
-               ];
-       }
-
-       function getOrderFields() {
-               // For some crazy reason ordering by a constant
-               // causes a filesort
-               if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       getContentNamespaces() ) > 1
-               ) {
-                       return [ 'page_namespace', 'page_title' ];
-               } else {
-                       return [ 'page_title' ];
-               }
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
index e9bf6a2..b2cd8c0 100644 (file)
@@ -28,7 +28,7 @@ use MediaWiki\MediaWikiServices;
  * Implements Special:DeletedContributions to display archived revisions
  * @ingroup SpecialPage
  */
-class DeletedContributionsPage extends SpecialPage {
+class SpecialDeletedContributions extends SpecialPage {
        /** @var FormOptions */
        protected $mOpts;
 
index fcf1bb2..cccca50 100644 (file)
@@ -30,7 +30,7 @@ use Wikimedia\Rdbms\IDatabase;
  *
  * @ingroup SpecialPage
  */
-class DoubleRedirectsPage extends QueryPage {
+class SpecialDoubleRedirects extends QueryPage {
        function __construct( $name = 'DoubleRedirects' ) {
                parent::__construct( $name );
        }
index 70a1bd4..1dd1969 100644 (file)
@@ -19,6 +19,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\Permissions\PermissionManager;
+
 /**
  * Special page for adding and removing change tags to individual revisions.
  * A lot of this is copied out of SpecialRevisiondelete.
@@ -51,8 +53,18 @@ class SpecialEditTags extends UnlistedSpecialPage {
        /** @var string */
        private $reason;
 
-       public function __construct() {
+       /** @var PermissionManager */
+       private $permissionManager;
+
+       /**
+        * @inheritDoc
+        *
+        * @param PermissionManager $permissionManager
+        */
+       public function __construct( PermissionManager $permissionManager ) {
                parent::__construct( 'EditTags', 'changetags' );
+
+               $this->permissionManager = $permissionManager;
        }
 
        public function doesWrites() {
@@ -67,13 +79,6 @@ class SpecialEditTags extends UnlistedSpecialPage {
                $user = $this->getUser();
                $request = $this->getRequest();
 
-               // Check blocks
-               // @TODO Use PermissionManager::isBlockedFrom() instead.
-               $block = $user->getBlock();
-               if ( $block ) {
-                       throw new UserBlockedError( $block );
-               }
-
                $this->setHeaders();
                $this->outputHeader();
 
@@ -132,6 +137,12 @@ class SpecialEditTags extends UnlistedSpecialPage {
                        $output->addWikiMsg( 'undelete-header' );
                        return;
                }
+
+               // Check blocks
+               if ( $this->permissionManager->isBlockedFrom( $user, $this->targetObj ) ) {
+                       throw new UserBlockedError( $user->getBlock() );
+               }
+
                // Give a link to the logs/hist for this page
                $this->showConvenienceLinks();
 
index c54abad..7b9ee1a 100644 (file)
@@ -27,7 +27,7 @@
  *
  * @ingroup SpecialPage
  */
-class EmailInvalidation extends UnlistedSpecialPage {
+class SpecialEmailInvalidate extends UnlistedSpecialPage {
        public function __construct() {
                parent::__construct( 'Invalidateemail', 'editmyprivateinfo' );
        }
index ef1b3d8..a466f29 100644 (file)
@@ -282,7 +282,7 @@ class SpecialExpandTemplates extends SpecialPage {
                        }
 
                        if ( $error ) {
-                               $out->wrapWikiMsg( "<div class='previewnote'>\n$1\n</div>", $error );
+                               $out->wrapWikiMsg( "<div class='previewnote errorbox'>\n$1\n</div>", $error );
                                return;
                        }
                }
diff --git a/includes/specials/SpecialFewestRevisions.php b/includes/specials/SpecialFewestRevisions.php
new file mode 100644 (file)
index 0000000..204d73a
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Implements Special:Fewestrevisions
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Special page for listing the articles with the fewest revisions.
+ *
+ * @ingroup SpecialPage
+ * @author Martin Drashkov
+ */
+class SpecialFewestRevisions extends QueryPage {
+       function __construct( $name = 'Fewestrevisions' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'revision', 'page' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'COUNT(*)',
+                       ],
+                       'conds' => [
+                               'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+                                       getContentNamespaces(),
+                               'page_id = rev_page',
+                               'page_is_redirect = 0',
+                       ],
+                       'options' => [
+                               'GROUP BY' => [ 'page_namespace', 'page_title' ]
+                       ]
+               ];
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Database row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $nt = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$nt ) {
+                       return Html::element(
+                               'span',
+                               [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription(
+                                       $this->getContext(),
+                                       $result->namespace,
+                                       $result->title
+                               )
+                       );
+               }
+               $linkRenderer = $this->getLinkRenderer();
+               $text = MediaWikiServices::getInstance()->getContentLanguage()->
+                       convert( htmlspecialchars( $nt->getPrefixedText() ) );
+               $plink = $linkRenderer->makeLink( $nt, new HtmlArmor( $text ) );
+
+               $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->text();
+               $redirect = isset( $result->redirect ) && $result->redirect ?
+                       ' - ' . $this->msg( 'isredirect' )->escaped() : '';
+               $nlink = $linkRenderer->makeKnownLink(
+                       $nt,
+                       $nl,
+                       [],
+                       [ 'action' => 'history' ]
+               ) . $redirect;
+
+               return $this->getLanguage()->specialList( $plink, $nlink );
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
deleted file mode 100644 (file)
index cf9da49..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-/**
- * Implements Special:Fewestrevisions
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Special page for listing the articles with the fewest revisions.
- *
- * @ingroup SpecialPage
- * @author Martin Drashkov
- */
-class FewestrevisionsPage extends QueryPage {
-       function __construct( $name = 'Fewestrevisions' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'revision', 'page' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'COUNT(*)',
-                       ],
-                       'conds' => [
-                               'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
-                                       getContentNamespaces(),
-                               'page_id = rev_page',
-                               'page_is_redirect = 0',
-                       ],
-                       'options' => [
-                               'GROUP BY' => [ 'page_namespace', 'page_title' ]
-                       ]
-               ];
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Database row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $nt = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$nt ) {
-                       return Html::element(
-                               'span',
-                               [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription(
-                                       $this->getContext(),
-                                       $result->namespace,
-                                       $result->title
-                               )
-                       );
-               }
-               $linkRenderer = $this->getLinkRenderer();
-               $text = MediaWikiServices::getInstance()->getContentLanguage()->
-                       convert( htmlspecialchars( $nt->getPrefixedText() ) );
-               $plink = $linkRenderer->makeLink( $nt, new HtmlArmor( $text ) );
-
-               $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->text();
-               $redirect = isset( $result->redirect ) && $result->redirect ?
-                       ' - ' . $this->msg( 'isredirect' )->escaped() : '';
-               $nlink = $linkRenderer->makeKnownLink(
-                       $nt,
-                       $nl,
-                       [],
-                       [ 'action' => 'history' ]
-               ) . $redirect;
-
-               return $this->getLanguage()->specialList( $plink, $nlink );
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
index 5d8a415..f047e1f 100644 (file)
@@ -31,7 +31,7 @@ use MediaWiki\MediaWikiServices;
  *
  * @ingroup SpecialPage
  */
-class FileDuplicateSearchPage extends QueryPage {
+class SpecialFileDuplicateSearch extends QueryPage {
        protected $hash = '', $filename = '';
 
        /**
index d08fe5c..60aedda 100644 (file)
@@ -29,9 +29,15 @@ use Wikimedia\Rdbms\IDatabase;
  * Special:LinkSearch to search the external-links table.
  * @ingroup SpecialPage
  */
-class LinkSearchPage extends QueryPage {
+class SpecialLinkSearch extends QueryPage {
        /** @var array|bool */
        private $mungedQuery = false;
+       /** @var string|null */
+       private $mQuery;
+       /** @var int|null */
+       private $mNs;
+       /** @var string|null */
+       private $mProt;
 
        function setParams( $params ) {
                $this->mQuery = $params['query'];
index c7430cc..d93c570 100644 (file)
@@ -32,7 +32,7 @@ use Wikimedia\Rdbms\IDatabase;
  *   a duplicate of the current version of some other file.
  * @ingroup SpecialPage
  */
-class ListDuplicatedFilesPage extends QueryPage {
+class SpecialListDuplicatedFiles extends QueryPage {
        function __construct( $name = 'ListDuplicatedFiles' ) {
                parent::__construct( $name );
        }
diff --git a/includes/specials/SpecialListRedirects.php b/includes/specials/SpecialListRedirects.php
new file mode 100644 (file)
index 0000000..e686273
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Implements Special:Listredirects
+ *
+ * Copyright © 2006 Rob Church
+ *
+ * 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 SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Special:Listredirects - Lists all the redirects on the wiki.
+ * @ingroup SpecialPage
+ */
+class SpecialListRedirects extends QueryPage {
+       function __construct( $name = 'Listredirects' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'p1' => 'page', 'redirect', 'p2' => 'page' ],
+                       'fields' => [ 'namespace' => 'p1.page_namespace',
+                               'title' => 'p1.page_title',
+                               'value' => 'p1.page_title',
+                               'rd_namespace',
+                               'rd_title',
+                               'rd_fragment',
+                               'rd_interwiki',
+                               'redirid' => 'p2.page_id' ],
+                       'conds' => [ 'p1.page_is_redirect' => 1 ],
+                       'join_conds' => [ 'redirect' => [
+                               'LEFT JOIN', 'rd_from=p1.page_id' ],
+                               'p2' => [ 'LEFT JOIN', [
+                                       'p2.page_namespace=rd_namespace',
+                                       'p2.page_title=rd_title' ] ] ]
+               ];
+       }
+
+       function getOrderFields() {
+               return [ 'p1.page_namespace', 'p1.page_title' ];
+       }
+
+       /**
+        * Cache page existence for performance
+        *
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               if ( !$res->numRows() ) {
+                       return;
+               }
+
+               $batch = new LinkBatch;
+               foreach ( $res as $row ) {
+                       $batch->add( $row->namespace, $row->title );
+                       $redirTarget = $this->getRedirectTarget( $row );
+                       if ( $redirTarget ) {
+                               $batch->addObj( $redirTarget );
+                       }
+               }
+               $batch->execute();
+
+               // Back to start for display
+               $res->seek( 0 );
+       }
+
+       /**
+        * @param stdClass $row
+        * @return Title|null
+        */
+       protected function getRedirectTarget( $row ) {
+               if ( isset( $row->rd_title ) ) {
+                       return Title::makeTitle( $row->rd_namespace,
+                               $row->rd_title, $row->rd_fragment,
+                               $row->rd_interwiki
+                       );
+               } else {
+                       $title = Title::makeTitle( $row->namespace, $row->title );
+                       $article = WikiPage::factory( $title );
+
+                       return $article->getRedirectTarget();
+               }
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $linkRenderer = $this->getLinkRenderer();
+               # Make a link to the redirect itself
+               $rd_title = Title::makeTitle( $result->namespace, $result->title );
+               $rd_link = $linkRenderer->makeLink(
+                       $rd_title,
+                       null,
+                       [],
+                       [ 'redirect' => 'no' ]
+               );
+
+               # Find out where the redirect leads
+               $target = $this->getRedirectTarget( $result );
+               if ( $target ) {
+                       # Make a link to the destination page
+                       $lang = $this->getLanguage();
+                       $arr = $lang->getArrow() . $lang->getDirMark();
+                       $targetLink = $linkRenderer->makeLink( $target, $target->getFullText() );
+
+                       return "$rd_link $arr $targetLink";
+               } else {
+                       return "<del>$rd_link</del>";
+               }
+       }
+
+       public function execute( $par ) {
+               $this->addHelpLink( 'Help:Redirects' );
+               parent::execute( $par );
+       }
+
+       protected function getGroupName() {
+               return 'pages';
+       }
+}
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
deleted file mode 100644 (file)
index 3284c57..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-/**
- * Implements Special:Listredirects
- *
- * Copyright © 2006 Rob Church
- *
- * 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 SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * Special:Listredirects - Lists all the redirects on the wiki.
- * @ingroup SpecialPage
- */
-class ListredirectsPage extends QueryPage {
-       function __construct( $name = 'Listredirects' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'p1' => 'page', 'redirect', 'p2' => 'page' ],
-                       'fields' => [ 'namespace' => 'p1.page_namespace',
-                               'title' => 'p1.page_title',
-                               'value' => 'p1.page_title',
-                               'rd_namespace',
-                               'rd_title',
-                               'rd_fragment',
-                               'rd_interwiki',
-                               'redirid' => 'p2.page_id' ],
-                       'conds' => [ 'p1.page_is_redirect' => 1 ],
-                       'join_conds' => [ 'redirect' => [
-                               'LEFT JOIN', 'rd_from=p1.page_id' ],
-                               'p2' => [ 'LEFT JOIN', [
-                                       'p2.page_namespace=rd_namespace',
-                                       'p2.page_title=rd_title' ] ] ]
-               ];
-       }
-
-       function getOrderFields() {
-               return [ 'p1.page_namespace', 'p1.page_title' ];
-       }
-
-       /**
-        * Cache page existence for performance
-        *
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       function preprocessResults( $db, $res ) {
-               if ( !$res->numRows() ) {
-                       return;
-               }
-
-               $batch = new LinkBatch;
-               foreach ( $res as $row ) {
-                       $batch->add( $row->namespace, $row->title );
-                       $redirTarget = $this->getRedirectTarget( $row );
-                       if ( $redirTarget ) {
-                               $batch->addObj( $redirTarget );
-                       }
-               }
-               $batch->execute();
-
-               // Back to start for display
-               $res->seek( 0 );
-       }
-
-       /**
-        * @param stdClass $row
-        * @return Title|null
-        */
-       protected function getRedirectTarget( $row ) {
-               if ( isset( $row->rd_title ) ) {
-                       return Title::makeTitle( $row->rd_namespace,
-                               $row->rd_title, $row->rd_fragment,
-                               $row->rd_interwiki
-                       );
-               } else {
-                       $title = Title::makeTitle( $row->namespace, $row->title );
-                       $article = WikiPage::factory( $title );
-
-                       return $article->getRedirectTarget();
-               }
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $linkRenderer = $this->getLinkRenderer();
-               # Make a link to the redirect itself
-               $rd_title = Title::makeTitle( $result->namespace, $result->title );
-               $rd_link = $linkRenderer->makeLink(
-                       $rd_title,
-                       null,
-                       [],
-                       [ 'redirect' => 'no' ]
-               );
-
-               # Find out where the redirect leads
-               $target = $this->getRedirectTarget( $result );
-               if ( $target ) {
-                       # Make a link to the destination page
-                       $lang = $this->getLanguage();
-                       $arr = $lang->getArrow() . $lang->getDirMark();
-                       $targetLink = $linkRenderer->makeLink( $target, $target->getFullText() );
-
-                       return "$rd_link $arr $targetLink";
-               } else {
-                       return "<del>$rd_link</del>";
-               }
-       }
-
-       public function execute( $par ) {
-               $this->addHelpLink( 'Help:Redirects' );
-               parent::execute( $par );
-       }
-
-       protected function getGroupName() {
-               return 'pages';
-       }
-}
index c6927c1..ac8baa1 100644 (file)
@@ -35,8 +35,6 @@ class SpecialLog extends SpecialPage {
        }
 
        public function execute( $par ) {
-               global $wgActorTableSchemaMigrationStage;
-
                $this->setHeaders();
                $this->outputHeader();
                $out = $this->getOutput();
@@ -107,13 +105,7 @@ class SpecialLog extends SpecialPage {
                        $offenderName = $opts->getValue( 'offender' );
                        $offender = empty( $offenderName ) ? null : User::newFromName( $offenderName, false );
                        if ( $offender ) {
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                                       $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
-                               } elseif ( $offender->getId() > 0 ) {
-                                       $qc = [ 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ];
-                               } else {
-                                       $qc = [ 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ];
-                               }
+                               $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
                        }
                } else {
                        // Allow extensions to add relations to their search types
diff --git a/includes/specials/SpecialLonelyPages.php b/includes/specials/SpecialLonelyPages.php
new file mode 100644 (file)
index 0000000..14f9435
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Implements Special:Lonelypaages
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A special page looking for articles with no article linking to them,
+ * thus being lonely.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialLonelyPages extends PageQueryPage {
+       function __construct( $name = 'Lonelypages' ) {
+               parent::__construct( $name );
+       }
+
+       function getPageHeader() {
+               return $this->msg( 'lonelypagestext' )->parseAsBlock();
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function getQueryInfo() {
+               $tables = [ 'page', 'pagelinks', 'templatelinks' ];
+               $conds = [
+                       'pl_namespace IS NULL',
+                       'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+                               getContentNamespaces(),
+                       'page_is_redirect' => 0,
+                       'tl_namespace IS NULL'
+               ];
+               $joinConds = [
+                       'pagelinks' => [
+                               'LEFT JOIN', [
+                                       'pl_namespace = page_namespace',
+                                       'pl_title = page_title'
+                               ]
+                       ],
+                       'templatelinks' => [
+                               'LEFT JOIN', [
+                                       'tl_namespace = page_namespace',
+                                       'tl_title = page_title'
+                               ]
+                       ]
+               ];
+
+               // Allow extensions to modify the query
+               Hooks::run( 'LonelyPagesQuery', [ &$tables, &$conds, &$joinConds ] );
+
+               return [
+                       'tables' => $tables,
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title'
+                       ],
+                       'conds' => $conds,
+                       'join_conds' => $joinConds
+               ];
+       }
+
+       function getOrderFields() {
+               // For some crazy reason ordering by a constant
+               // causes a filesort in MySQL 5
+               if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
+                       getContentNamespaces() ) > 1
+               ) {
+                       return [ 'page_namespace', 'page_title' ];
+               } else {
+                       return [ 'page_title' ];
+               }
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
deleted file mode 100644 (file)
index ca3f4da..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * Implements Special:Lonelypaages
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A special page looking for articles with no article linking to them,
- * thus being lonely.
- *
- * @ingroup SpecialPage
- */
-class LonelyPagesPage extends PageQueryPage {
-       function __construct( $name = 'Lonelypages' ) {
-               parent::__construct( $name );
-       }
-
-       function getPageHeader() {
-               return $this->msg( 'lonelypagestext' )->parseAsBlock();
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function getQueryInfo() {
-               $tables = [ 'page', 'pagelinks', 'templatelinks' ];
-               $conds = [
-                       'pl_namespace IS NULL',
-                       'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
-                               getContentNamespaces(),
-                       'page_is_redirect' => 0,
-                       'tl_namespace IS NULL'
-               ];
-               $joinConds = [
-                       'pagelinks' => [
-                               'LEFT JOIN', [
-                                       'pl_namespace = page_namespace',
-                                       'pl_title = page_title'
-                               ]
-                       ],
-                       'templatelinks' => [
-                               'LEFT JOIN', [
-                                       'tl_namespace = page_namespace',
-                                       'tl_title = page_title'
-                               ]
-                       ]
-               ];
-
-               // Allow extensions to modify the query
-               Hooks::run( 'LonelyPagesQuery', [ &$tables, &$conds, &$joinConds ] );
-
-               return [
-                       'tables' => $tables,
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title'
-                       ],
-                       'conds' => $conds,
-                       'join_conds' => $joinConds
-               ];
-       }
-
-       function getOrderFields() {
-               // For some crazy reason ordering by a constant
-               // causes a filesort in MySQL 5
-               if ( count( MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       getContentNamespaces() ) > 1
-               ) {
-                       return [ 'page_namespace', 'page_title' ];
-               } else {
-                       return [ 'page_title' ];
-               }
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialLongPages.php b/includes/specials/SpecialLongPages.php
new file mode 100644 (file)
index 0000000..5227d4e
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Implements Special:Longpages
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialLongPages extends SpecialShortPages {
+       function __construct( $name = 'Longpages' ) {
+               parent::__construct( $name );
+       }
+
+       function sortDescending() {
+               return true;
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php
deleted file mode 100644 (file)
index d90d271..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-/**
- * Implements Special:Longpages
- *
- * 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 SpecialPage
- */
-
-/**
- *
- * @ingroup SpecialPage
- */
-class LongPagesPage extends ShortPagesPage {
-       function __construct( $name = 'Longpages' ) {
-               parent::__construct( $name );
-       }
-
-       function sortDescending() {
-               return true;
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialMIMESearch.php b/includes/specials/SpecialMIMESearch.php
new file mode 100644 (file)
index 0000000..0784821
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Implements Special:MIMESearch
+ *
+ * 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 SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Searches the database for files of the requested MIME type, comparing this with the
+ * 'img_major_mime' and 'img_minor_mime' fields in the image table.
+ * @ingroup SpecialPage
+ */
+class SpecialMIMESearch extends QueryPage {
+       protected $major, $minor, $mime;
+
+       function __construct( $name = 'MIMEsearch' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return false;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function isCacheable() {
+               return false;
+       }
+
+       function linkParameters() {
+               return [ 'mime' => "{$this->major}/{$this->minor}" ];
+       }
+
+       public function getQueryInfo() {
+               $minorType = [];
+               if ( $this->minor !== '*' ) {
+                       // Allow wildcard searching
+                       $minorType['img_minor_mime'] = $this->minor;
+               }
+               $imgQuery = LocalFile::getQueryInfo();
+               $qi = [
+                       'tables' => $imgQuery['tables'],
+                       'fields' => [
+                               'namespace' => NS_FILE,
+                               'title' => 'img_name',
+                               // Still have a value field just in case,
+                               // but it isn't actually used for sorting.
+                               'value' => 'img_name',
+                               'img_size',
+                               'img_width',
+                               'img_height',
+                               'img_user_text' => $imgQuery['fields']['img_user_text'],
+                               'img_timestamp'
+                       ],
+                       'conds' => [
+                               'img_major_mime' => $this->major,
+                               // This is in order to trigger using
+                               // the img_media_mime index in "range" mode.
+                               // @todo how is order defined? use MimeAnalyzer::getMediaTypes?
+                               'img_media_type' => [
+                                       MEDIATYPE_BITMAP,
+                                       MEDIATYPE_DRAWING,
+                                       MEDIATYPE_AUDIO,
+                                       MEDIATYPE_VIDEO,
+                                       MEDIATYPE_MULTIMEDIA,
+                                       MEDIATYPE_UNKNOWN,
+                                       MEDIATYPE_OFFICE,
+                                       MEDIATYPE_TEXT,
+                                       MEDIATYPE_EXECUTABLE,
+                                       MEDIATYPE_ARCHIVE,
+                                       MEDIATYPE_3D,
+                               ],
+                       ] + $minorType,
+                       'join_conds' => $imgQuery['joins'],
+               ];
+
+               return $qi;
+       }
+
+       /**
+        * The index is on (img_media_type, img_major_mime, img_minor_mime)
+        * which unfortunately doesn't have img_name at the end for sorting.
+        * So tell db to sort it however it wishes (Its not super important
+        * that this report gives results in a logical order). As an aditional
+        * note, mysql seems to by default order things by img_name ASC, which
+        * is what we ideally want, so everything works out fine anyhow.
+        * @return array
+        */
+       function getOrderFields() {
+               return [];
+       }
+
+       /**
+        * Generate and output the form
+        */
+       function getPageHeader() {
+               $formDescriptor = [
+                       'mime' => [
+                               'type' => 'combobox',
+                               'options' => $this->getSuggestionsForTypes(),
+                               'name' => 'mime',
+                               'label-message' => 'mimetype',
+                               'required' => true,
+                               'default' => $this->mime,
+                       ],
+               ];
+
+               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setSubmitTextMsg( 'ilsubmit' )
+                       ->setAction( $this->getPageTitle()->getLocalURL() )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
+               return '';
+       }
+
+       protected function getSuggestionsForTypes() {
+               $dbr = wfGetDB( DB_REPLICA );
+               $lastMajor = null;
+               $suggestions = [];
+               $result = $dbr->select(
+                       [ 'image' ],
+                       // We ignore img_media_type, but using it in the query is needed for MySQL to choose a
+                       // sensible execution plan
+                       [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ],
+                       [],
+                       __METHOD__,
+                       [ 'GROUP BY' => [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ] ]
+               );
+               foreach ( $result as $row ) {
+                       $major = $row->img_major_mime;
+                       $minor = $row->img_minor_mime;
+                       $suggestions[ "$major/$minor" ] = "$major/$minor";
+                       if ( $lastMajor === $major ) {
+                               // If there are at least two with the same major mime type, also include the wildcard
+                               $suggestions[ "$major/*" ] = "$major/*";
+                       }
+                       $lastMajor = $major;
+               }
+               ksort( $suggestions );
+               return $suggestions;
+       }
+
+       public function execute( $par ) {
+               $this->addHelpLink( 'Help:Managing_files' );
+               $this->mime = $par ?: $this->getRequest()->getText( 'mime' );
+               $this->mime = trim( $this->mime );
+               list( $this->major, $this->minor ) = File::splitMime( $this->mime );
+
+               if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
+                       !self::isValidType( $this->major )
+               ) {
+                       $this->setHeaders();
+                       $this->outputHeader();
+                       $this->getPageHeader();
+                       return;
+               }
+
+               parent::execute( $par );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $linkRenderer = $this->getLinkRenderer();
+               $nt = Title::makeTitle( $result->namespace, $result->title );
+               $text = MediaWikiServices::getInstance()->getContentLanguage()
+                       ->convert( htmlspecialchars( $nt->getText() ) );
+               $plink = $linkRenderer->makeLink(
+                       Title::newFromText( $nt->getPrefixedText() ),
+                       new HtmlArmor( $text )
+               );
+
+               $download = Linker::makeMediaLinkObj( $nt, $this->msg( 'download' )->escaped() );
+               $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
+               $lang = $this->getLanguage();
+               $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
+               $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
+                       $result->img_height )->escaped();
+               $user = $linkRenderer->makeLink(
+                       Title::makeTitle( NS_USER, $result->img_user_text ),
+                       $result->img_user_text
+               );
+
+               $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
+               $time = htmlspecialchars( $time );
+
+               return "$download $plink . . $dimensions . . $bytes . . $user . . $time";
+       }
+
+       /**
+        * @param string $type
+        * @return bool
+        */
+       protected static function isValidType( $type ) {
+               // From maintenance/tables.sql => img_major_mime
+               $types = [
+                       'unknown',
+                       'application',
+                       'audio',
+                       'image',
+                       'text',
+                       'video',
+                       'message',
+                       'model',
+                       'multipart',
+                       'chemical'
+               ];
+
+               return in_array( $type, $types );
+       }
+
+       public function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       protected function getGroupName() {
+               return 'media';
+       }
+}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
deleted file mode 100644 (file)
index d6ace1d..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-<?php
-/**
- * Implements Special:MIMESearch
- *
- * 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 SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Searches the database for files of the requested MIME type, comparing this with the
- * 'img_major_mime' and 'img_minor_mime' fields in the image table.
- * @ingroup SpecialPage
- */
-class MIMEsearchPage extends QueryPage {
-       protected $major, $minor, $mime;
-
-       function __construct( $name = 'MIMEsearch' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return false;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function isCacheable() {
-               return false;
-       }
-
-       function linkParameters() {
-               return [ 'mime' => "{$this->major}/{$this->minor}" ];
-       }
-
-       public function getQueryInfo() {
-               $minorType = [];
-               if ( $this->minor !== '*' ) {
-                       // Allow wildcard searching
-                       $minorType['img_minor_mime'] = $this->minor;
-               }
-               $imgQuery = LocalFile::getQueryInfo();
-               $qi = [
-                       'tables' => $imgQuery['tables'],
-                       'fields' => [
-                               'namespace' => NS_FILE,
-                               'title' => 'img_name',
-                               // Still have a value field just in case,
-                               // but it isn't actually used for sorting.
-                               'value' => 'img_name',
-                               'img_size',
-                               'img_width',
-                               'img_height',
-                               'img_user_text' => $imgQuery['fields']['img_user_text'],
-                               'img_timestamp'
-                       ],
-                       'conds' => [
-                               'img_major_mime' => $this->major,
-                               // This is in order to trigger using
-                               // the img_media_mime index in "range" mode.
-                               // @todo how is order defined? use MimeAnalyzer::getMediaTypes?
-                               'img_media_type' => [
-                                       MEDIATYPE_BITMAP,
-                                       MEDIATYPE_DRAWING,
-                                       MEDIATYPE_AUDIO,
-                                       MEDIATYPE_VIDEO,
-                                       MEDIATYPE_MULTIMEDIA,
-                                       MEDIATYPE_UNKNOWN,
-                                       MEDIATYPE_OFFICE,
-                                       MEDIATYPE_TEXT,
-                                       MEDIATYPE_EXECUTABLE,
-                                       MEDIATYPE_ARCHIVE,
-                                       MEDIATYPE_3D,
-                               ],
-                       ] + $minorType,
-                       'join_conds' => $imgQuery['joins'],
-               ];
-
-               return $qi;
-       }
-
-       /**
-        * The index is on (img_media_type, img_major_mime, img_minor_mime)
-        * which unfortunately doesn't have img_name at the end for sorting.
-        * So tell db to sort it however it wishes (Its not super important
-        * that this report gives results in a logical order). As an aditional
-        * note, mysql seems to by default order things by img_name ASC, which
-        * is what we ideally want, so everything works out fine anyhow.
-        * @return array
-        */
-       function getOrderFields() {
-               return [];
-       }
-
-       /**
-        * Generate and output the form
-        */
-       function getPageHeader() {
-               $formDescriptor = [
-                       'mime' => [
-                               'type' => 'combobox',
-                               'options' => $this->getSuggestionsForTypes(),
-                               'name' => 'mime',
-                               'label-message' => 'mimetype',
-                               'required' => true,
-                               'default' => $this->mime,
-                       ],
-               ];
-
-               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
-                       ->setSubmitTextMsg( 'ilsubmit' )
-                       ->setAction( $this->getPageTitle()->getLocalURL() )
-                       ->setMethod( 'get' )
-                       ->prepareForm()
-                       ->displayForm( false );
-               return '';
-       }
-
-       protected function getSuggestionsForTypes() {
-               $dbr = wfGetDB( DB_REPLICA );
-               $lastMajor = null;
-               $suggestions = [];
-               $result = $dbr->select(
-                       [ 'image' ],
-                       // We ignore img_media_type, but using it in the query is needed for MySQL to choose a
-                       // sensible execution plan
-                       [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ],
-                       [],
-                       __METHOD__,
-                       [ 'GROUP BY' => [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ] ]
-               );
-               foreach ( $result as $row ) {
-                       $major = $row->img_major_mime;
-                       $minor = $row->img_minor_mime;
-                       $suggestions[ "$major/$minor" ] = "$major/$minor";
-                       if ( $lastMajor === $major ) {
-                               // If there are at least two with the same major mime type, also include the wildcard
-                               $suggestions[ "$major/*" ] = "$major/*";
-                       }
-                       $lastMajor = $major;
-               }
-               ksort( $suggestions );
-               return $suggestions;
-       }
-
-       public function execute( $par ) {
-               $this->addHelpLink( 'Help:Managing_files' );
-               $this->mime = $par ?: $this->getRequest()->getText( 'mime' );
-               $this->mime = trim( $this->mime );
-               list( $this->major, $this->minor ) = File::splitMime( $this->mime );
-
-               if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
-                       !self::isValidType( $this->major )
-               ) {
-                       $this->setHeaders();
-                       $this->outputHeader();
-                       $this->getPageHeader();
-                       return;
-               }
-
-               parent::execute( $par );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $linkRenderer = $this->getLinkRenderer();
-               $nt = Title::makeTitle( $result->namespace, $result->title );
-               $text = MediaWikiServices::getInstance()->getContentLanguage()
-                       ->convert( htmlspecialchars( $nt->getText() ) );
-               $plink = $linkRenderer->makeLink(
-                       Title::newFromText( $nt->getPrefixedText() ),
-                       new HtmlArmor( $text )
-               );
-
-               $download = Linker::makeMediaLinkObj( $nt, $this->msg( 'download' )->escaped() );
-               $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
-               $lang = $this->getLanguage();
-               $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
-               $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
-                       $result->img_height )->escaped();
-               $user = $linkRenderer->makeLink(
-                       Title::makeTitle( NS_USER, $result->img_user_text ),
-                       $result->img_user_text
-               );
-
-               $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
-               $time = htmlspecialchars( $time );
-
-               return "$download $plink . . $dimensions . . $bytes . . $user . . $time";
-       }
-
-       /**
-        * @param string $type
-        * @return bool
-        */
-       protected static function isValidType( $type ) {
-               // From maintenance/tables.sql => img_major_mime
-               $types = [
-                       'unknown',
-                       'application',
-                       'audio',
-                       'image',
-                       'text',
-                       'video',
-                       'message',
-                       'model',
-                       'multipart',
-                       'chemical'
-               ];
-
-               return in_array( $type, $types );
-       }
-
-       public function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       protected function getGroupName() {
-               return 'media';
-       }
-}
index 45bd524..333d9b3 100644 (file)
@@ -28,7 +28,7 @@ use Wikimedia\Rdbms\IDatabase;
 /**
  * @ingroup SpecialPage
  */
-class MediaStatisticsPage extends QueryPage {
+class SpecialMediaStatistics extends QueryPage {
        protected $totalCount = 0, $totalBytes = 0;
 
        /**
diff --git a/includes/specials/SpecialMostCategories.php b/includes/specials/SpecialMostCategories.php
new file mode 100644 (file)
index 0000000..682a23a
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Implements Special:Mostcategories
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * 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 SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page that list pages that have highest category count
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostCategories extends QueryPage {
+       function __construct( $name = 'Mostcategories' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'categorylinks', 'page' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'COUNT(*)'
+                       ],
+                       'conds' => [ 'page_namespace' =>
+                               MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces() ],
+                       'options' => [
+                               'HAVING' => 'COUNT(*) > 1',
+                               'GROUP BY' => [ 'page_namespace', 'page_title' ]
+                       ],
+                       'join_conds' => [
+                               'page' => [
+                                       'LEFT JOIN',
+                                       'page_id = cl_from'
+                               ]
+                       ]
+               ];
+       }
+
+       /**
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $title = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$title ) {
+                       return Html::element(
+                               'span',
+                               [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription(
+                                       $this->getContext(),
+                                       $result->namespace,
+                                       $result->title
+                               )
+                       );
+               }
+
+               $linkRenderer = $this->getLinkRenderer();
+               if ( $this->isCached() ) {
+                       $link = $linkRenderer->makeLink( $title );
+               } else {
+                       $link = $linkRenderer->makeKnownLink( $title );
+               }
+
+               $count = $this->msg( 'ncategories' )->numParams( $result->value )->escaped();
+
+               return $this->getLanguage()->specialList( $link, $count );
+       }
+
+       protected function getGroupName() {
+               return 'highuse';
+       }
+}
diff --git a/includes/specials/SpecialMostInterwikis.php b/includes/specials/SpecialMostInterwikis.php
new file mode 100644 (file)
index 0000000..52777b0
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Implements Special:Mostinterwikis
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page that listed pages that have highest interwiki count
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostInterwikis extends QueryPage {
+       function __construct( $name = 'Mostinterwikis' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [
+                               'langlinks',
+                               'page'
+                       ], 'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'COUNT(*)'
+                       ], 'conds' => [
+                               'page_namespace' =>
+                                       MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces()
+                       ], 'options' => [
+                               'HAVING' => 'COUNT(*) > 1',
+                               'GROUP BY' => [
+                                       'page_namespace',
+                                       'page_title'
+                               ]
+                       ], 'join_conds' => [
+                               'page' => [
+                                       'LEFT JOIN',
+                                       'page_id = ll_from'
+                               ]
+                       ]
+               ];
+       }
+
+       /**
+        * Pre-fill the link cache
+        *
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $title = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$title ) {
+                       return Html::element(
+                               'span',
+                               [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription(
+                                       $this->getContext(),
+                                       $result->namespace,
+                                       $result->title
+                               )
+                       );
+               }
+
+               $linkRenderer = $this->getLinkRenderer();
+               if ( $this->isCached() ) {
+                       $link = $linkRenderer->makeLink( $title );
+               } else {
+                       $link = $linkRenderer->makeKnownLink( $title );
+               }
+
+               $count = $this->msg( 'ninterwikis' )->numParams( $result->value )->escaped();
+
+               return $this->getLanguage()->specialList( $link, $count );
+       }
+
+       protected function getGroupName() {
+               return 'highuse';
+       }
+}
diff --git a/includes/specials/SpecialMostLinked.php b/includes/specials/SpecialMostLinked.php
new file mode 100644 (file)
index 0000000..c0f1a2a
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Implements Special:Mostlinked
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason, 2006 Rob Church
+ *
+ * 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 SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page to show pages ordered by the number of pages linking to them.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostLinked extends QueryPage {
+       function __construct( $name = 'Mostlinked' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'pagelinks', 'page' ],
+                       'fields' => [
+                               'namespace' => 'pl_namespace',
+                               'title' => 'pl_title',
+                               'value' => 'COUNT(*)',
+                               'page_namespace'
+                       ],
+                       'options' => [
+                               'HAVING' => 'COUNT(*) > 1',
+                               'GROUP BY' => [
+                                       'pl_namespace', 'pl_title',
+                                       'page_namespace'
+                               ]
+                       ],
+                       'join_conds' => [
+                               'page' => [
+                                       'LEFT JOIN',
+                                       [
+                                               'page_namespace = pl_namespace',
+                                               'page_title = pl_title'
+                                       ]
+                               ]
+                       ]
+               ];
+       }
+
+       /**
+        * Pre-fill the link cache
+        *
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       /**
+        * Make a link to "what links here" for the specified title
+        *
+        * @param Title $title Title being queried
+        * @param string $caption Text to display on the link
+        * @return string
+        */
+       function makeWlhLink( $title, $caption ) {
+               $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
+
+               $linkRenderer = $this->getLinkRenderer();
+               return $linkRenderer->makeKnownLink( $wlh, $caption );
+       }
+
+       /**
+        * Make links to the page corresponding to the item,
+        * and the "what links here" page for it
+        *
+        * @param Skin $skin Skin to be used
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $title = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$title ) {
+                       return Html::element(
+                               'span',
+                               [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription(
+                                       $this->getContext(),
+                                       $result->namespace,
+                                       $result->title )
+                       );
+               }
+
+               $linkRenderer = $this->getLinkRenderer();
+               $link = $linkRenderer->makeLink( $title );
+               $wlh = $this->makeWlhLink(
+                       $title,
+                       $this->msg( 'nlinks' )->numParams( $result->value )->text()
+               );
+
+               return $this->getLanguage()->specialList( $link, $wlh );
+       }
+
+       protected function getGroupName() {
+               return 'highuse';
+       }
+}
diff --git a/includes/specials/SpecialMostLinkedCategories.php b/includes/specials/SpecialMostLinkedCategories.php
new file mode 100644 (file)
index 0000000..32157fa
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Implements Special:Mostlinkedcategories
+ *
+ * Copyright © 2005, Ævar Arnfjörð Bjarmason
+ *
+ * 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 SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A querypage to show categories ordered in descending order by the pages in them
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostLinkedCategories extends QueryPage {
+       function __construct( $name = 'Mostlinkedcategories' ) {
+               parent::__construct( $name );
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'category' ],
+                       'fields' => [ 'title' => 'cat_title',
+                               'namespace' => NS_CATEGORY,
+                               'value' => 'cat_pages' ],
+                       'conds' => [ 'cat_pages > 0' ],
+               ];
+       }
+
+       function sortDescending() {
+               return true;
+       }
+
+       /**
+        * Fetch user page links and cache their existence
+        *
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $nt = Title::makeTitleSafe( NS_CATEGORY, $result->title );
+               if ( !$nt ) {
+                       return Html::element(
+                               'span',
+                               [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription(
+                                       $this->getContext(),
+                                       NS_CATEGORY,
+                                       $result->title )
+                       );
+               }
+
+               $text = MediaWikiServices::getInstance()->getContentLanguage()
+                       ->convert( htmlspecialchars( $nt->getText() ) );
+               $plink = $this->getLinkRenderer()->makeLink( $nt, new HtmlArmor( $text ) );
+               $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+
+               return $this->getLanguage()->specialList( $plink, $nlinks );
+       }
+
+       protected function getGroupName() {
+               return 'highuse';
+       }
+}
diff --git a/includes/specials/SpecialMostLinkedTemplates.php b/includes/specials/SpecialMostLinkedTemplates.php
new file mode 100644 (file)
index 0000000..f7122c2
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Implements Special:Mostlinkedtemplates
+ *
+ * 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 SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Special page lists templates with a large number of
+ * transclusion links, i.e. "most used" templates
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMostLinkedTemplates extends QueryPage {
+       function __construct( $name = 'Mostlinkedtemplates' ) {
+               parent::__construct( $name );
+       }
+
+       /**
+        * Is this report expensive, i.e should it be cached?
+        *
+        * @return bool
+        */
+       public function isExpensive() {
+               return true;
+       }
+
+       /**
+        * Is there a feed available?
+        *
+        * @return bool
+        */
+       public function isSyndicated() {
+               return false;
+       }
+
+       /**
+        * Sort the results in descending order?
+        *
+        * @return bool
+        */
+       public function sortDescending() {
+               return true;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'templatelinks' ],
+                       'fields' => [
+                               'namespace' => 'tl_namespace',
+                               'title' => 'tl_title',
+                               'value' => 'COUNT(*)'
+                       ],
+                       'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ]
+               ];
+       }
+
+       /**
+        * Pre-cache page existence to speed up link generation
+        *
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       public function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       /**
+        * Format a result row
+        *
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       public function formatResult( $skin, $result ) {
+               $title = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$title ) {
+                       return Html::element(
+                               'span',
+                               [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription(
+                                       $this->getContext(),
+                                       $result->namespace,
+                                       $result->title
+                               )
+                       );
+               }
+
+               return $this->getLanguage()->specialList(
+                       $this->getLinkRenderer()->makeLink( $title ),
+                       $this->makeWlhLink( $title, $result )
+               );
+       }
+
+       /**
+        * Make a "what links here" link for a given title
+        *
+        * @param Title $title Title to make the link for
+        * @param object $result Result row
+        * @return string
+        */
+       private function makeWlhLink( $title, $result ) {
+               $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
+               $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->text();
+
+               return $this->getLinkRenderer()->makeLink( $wlh, $label );
+       }
+
+       protected function getGroupName() {
+               return 'highuse';
+       }
+}
diff --git a/includes/specials/SpecialMostRevisions.php b/includes/specials/SpecialMostRevisions.php
new file mode 100644 (file)
index 0000000..60e0fe7
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Implements Special:Mostrevisions
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * 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 SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+class SpecialMostRevisions extends SpecialFewestRevisions {
+       function __construct( $name = 'Mostrevisions' ) {
+               parent::__construct( $name );
+       }
+
+       function sortDescending() {
+               return true;
+       }
+
+       protected function getGroupName() {
+               return 'highuse';
+       }
+}
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
deleted file mode 100644 (file)
index 0dd9437..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/**
- * Implements Special:Mostcategories
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * 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 SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page that list pages that have highest category count
- *
- * @ingroup SpecialPage
- */
-class MostcategoriesPage extends QueryPage {
-       function __construct( $name = 'Mostcategories' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'categorylinks', 'page' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'COUNT(*)'
-                       ],
-                       'conds' => [ 'page_namespace' =>
-                               MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces() ],
-                       'options' => [
-                               'HAVING' => 'COUNT(*) > 1',
-                               'GROUP BY' => [ 'page_namespace', 'page_title' ]
-                       ],
-                       'join_conds' => [
-                               'page' => [
-                                       'LEFT JOIN',
-                                       'page_id = cl_from'
-                               ]
-                       ]
-               ];
-       }
-
-       /**
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $title = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$title ) {
-                       return Html::element(
-                               'span',
-                               [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription(
-                                       $this->getContext(),
-                                       $result->namespace,
-                                       $result->title
-                               )
-                       );
-               }
-
-               $linkRenderer = $this->getLinkRenderer();
-               if ( $this->isCached() ) {
-                       $link = $linkRenderer->makeLink( $title );
-               } else {
-                       $link = $linkRenderer->makeKnownLink( $title );
-               }
-
-               $count = $this->msg( 'ncategories' )->numParams( $result->value )->escaped();
-
-               return $this->getLanguage()->specialList( $link, $count );
-       }
-
-       protected function getGroupName() {
-               return 'highuse';
-       }
-}
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
deleted file mode 100644 (file)
index 0fcf842..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-/**
- * Implements Special:Mostinterwikis
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page that listed pages that have highest interwiki count
- *
- * @ingroup SpecialPage
- */
-class MostinterwikisPage extends QueryPage {
-       function __construct( $name = 'Mostinterwikis' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [
-                               'langlinks',
-                               'page'
-                       ], 'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'COUNT(*)'
-                       ], 'conds' => [
-                               'page_namespace' =>
-                                       MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces()
-                       ], 'options' => [
-                               'HAVING' => 'COUNT(*) > 1',
-                               'GROUP BY' => [
-                                       'page_namespace',
-                                       'page_title'
-                               ]
-                       ], 'join_conds' => [
-                               'page' => [
-                                       'LEFT JOIN',
-                                       'page_id = ll_from'
-                               ]
-                       ]
-               ];
-       }
-
-       /**
-        * Pre-fill the link cache
-        *
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $title = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$title ) {
-                       return Html::element(
-                               'span',
-                               [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription(
-                                       $this->getContext(),
-                                       $result->namespace,
-                                       $result->title
-                               )
-                       );
-               }
-
-               $linkRenderer = $this->getLinkRenderer();
-               if ( $this->isCached() ) {
-                       $link = $linkRenderer->makeLink( $title );
-               } else {
-                       $link = $linkRenderer->makeKnownLink( $title );
-               }
-
-               $count = $this->msg( 'ninterwikis' )->numParams( $result->value )->escaped();
-
-               return $this->getLanguage()->specialList( $link, $count );
-       }
-
-       protected function getGroupName() {
-               return 'highuse';
-       }
-}
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
deleted file mode 100644 (file)
index c4553a4..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-/**
- * Implements Special:Mostlinked
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason, 2006 Rob Church
- *
- * 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 SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @author Rob Church <robchur@gmail.com>
- */
-
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page to show pages ordered by the number of pages linking to them.
- *
- * @ingroup SpecialPage
- */
-class MostlinkedPage extends QueryPage {
-       function __construct( $name = 'Mostlinked' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'pagelinks', 'page' ],
-                       'fields' => [
-                               'namespace' => 'pl_namespace',
-                               'title' => 'pl_title',
-                               'value' => 'COUNT(*)',
-                               'page_namespace'
-                       ],
-                       'options' => [
-                               'HAVING' => 'COUNT(*) > 1',
-                               'GROUP BY' => [
-                                       'pl_namespace', 'pl_title',
-                                       'page_namespace'
-                               ]
-                       ],
-                       'join_conds' => [
-                               'page' => [
-                                       'LEFT JOIN',
-                                       [
-                                               'page_namespace = pl_namespace',
-                                               'page_title = pl_title'
-                                       ]
-                               ]
-                       ]
-               ];
-       }
-
-       /**
-        * Pre-fill the link cache
-        *
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       /**
-        * Make a link to "what links here" for the specified title
-        *
-        * @param Title $title Title being queried
-        * @param string $caption Text to display on the link
-        * @return string
-        */
-       function makeWlhLink( $title, $caption ) {
-               $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
-
-               $linkRenderer = $this->getLinkRenderer();
-               return $linkRenderer->makeKnownLink( $wlh, $caption );
-       }
-
-       /**
-        * Make links to the page corresponding to the item,
-        * and the "what links here" page for it
-        *
-        * @param Skin $skin Skin to be used
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $title = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$title ) {
-                       return Html::element(
-                               'span',
-                               [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription(
-                                       $this->getContext(),
-                                       $result->namespace,
-                                       $result->title )
-                       );
-               }
-
-               $linkRenderer = $this->getLinkRenderer();
-               $link = $linkRenderer->makeLink( $title );
-               $wlh = $this->makeWlhLink(
-                       $title,
-                       $this->msg( 'nlinks' )->numParams( $result->value )->text()
-               );
-
-               return $this->getLanguage()->specialList( $link, $wlh );
-       }
-
-       protected function getGroupName() {
-               return 'highuse';
-       }
-}
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
deleted file mode 100644 (file)
index 56a701a..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-/**
- * Implements Special:Mostlinkedcategories
- *
- * Copyright © 2005, Ævar Arnfjörð Bjarmason
- *
- * 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 SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A querypage to show categories ordered in descending order by the pages in them
- *
- * @ingroup SpecialPage
- */
-class MostlinkedCategoriesPage extends QueryPage {
-       function __construct( $name = 'Mostlinkedcategories' ) {
-               parent::__construct( $name );
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'category' ],
-                       'fields' => [ 'title' => 'cat_title',
-                               'namespace' => NS_CATEGORY,
-                               'value' => 'cat_pages' ],
-                       'conds' => [ 'cat_pages > 0' ],
-               ];
-       }
-
-       function sortDescending() {
-               return true;
-       }
-
-       /**
-        * Fetch user page links and cache their existence
-        *
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $nt = Title::makeTitleSafe( NS_CATEGORY, $result->title );
-               if ( !$nt ) {
-                       return Html::element(
-                               'span',
-                               [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription(
-                                       $this->getContext(),
-                                       NS_CATEGORY,
-                                       $result->title )
-                       );
-               }
-
-               $text = MediaWikiServices::getInstance()->getContentLanguage()
-                       ->convert( htmlspecialchars( $nt->getText() ) );
-               $plink = $this->getLinkRenderer()->makeLink( $nt, new HtmlArmor( $text ) );
-               $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
-
-               return $this->getLanguage()->specialList( $plink, $nlinks );
-       }
-
-       protected function getGroupName() {
-               return 'highuse';
-       }
-}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
deleted file mode 100644 (file)
index 4544468..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-/**
- * Implements Special:Mostlinkedtemplates
- *
- * 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 SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * Special page lists templates with a large number of
- * transclusion links, i.e. "most used" templates
- *
- * @ingroup SpecialPage
- */
-class MostlinkedTemplatesPage extends QueryPage {
-       function __construct( $name = 'Mostlinkedtemplates' ) {
-               parent::__construct( $name );
-       }
-
-       /**
-        * Is this report expensive, i.e should it be cached?
-        *
-        * @return bool
-        */
-       public function isExpensive() {
-               return true;
-       }
-
-       /**
-        * Is there a feed available?
-        *
-        * @return bool
-        */
-       public function isSyndicated() {
-               return false;
-       }
-
-       /**
-        * Sort the results in descending order?
-        *
-        * @return bool
-        */
-       public function sortDescending() {
-               return true;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'templatelinks' ],
-                       'fields' => [
-                               'namespace' => 'tl_namespace',
-                               'title' => 'tl_title',
-                               'value' => 'COUNT(*)'
-                       ],
-                       'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ]
-               ];
-       }
-
-       /**
-        * Pre-cache page existence to speed up link generation
-        *
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       public function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       /**
-        * Format a result row
-        *
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       public function formatResult( $skin, $result ) {
-               $title = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$title ) {
-                       return Html::element(
-                               'span',
-                               [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription(
-                                       $this->getContext(),
-                                       $result->namespace,
-                                       $result->title
-                               )
-                       );
-               }
-
-               return $this->getLanguage()->specialList(
-                       $this->getLinkRenderer()->makeLink( $title ),
-                       $this->makeWlhLink( $title, $result )
-               );
-       }
-
-       /**
-        * Make a "what links here" link for a given title
-        *
-        * @param Title $title Title to make the link for
-        * @param object $result Result row
-        * @return string
-        */
-       private function makeWlhLink( $title, $result ) {
-               $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
-               $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->text();
-
-               return $this->getLinkRenderer()->makeLink( $wlh, $label );
-       }
-
-       protected function getGroupName() {
-               return 'highuse';
-       }
-}
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
deleted file mode 100644 (file)
index 0471caf..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-/**
- * Implements Special:Mostrevisions
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * 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 SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class MostrevisionsPage extends FewestrevisionsPage {
-       function __construct( $name = 'Mostrevisions' ) {
-               parent::__construct( $name );
-       }
-
-       function sortDescending() {
-               return true;
-       }
-
-       protected function getGroupName() {
-               return 'highuse';
-       }
-}
diff --git a/includes/specials/SpecialNewFiles.php b/includes/specials/SpecialNewFiles.php
new file mode 100644 (file)
index 0000000..29e7789
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+/**
+ * Implements Special:Newimages
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+class SpecialNewFiles extends IncludableSpecialPage {
+       /** @var FormOptions */
+       protected $opts;
+
+       /** @var string[] */
+       protected $mediaTypes;
+
+       public function __construct() {
+               parent::__construct( 'Newimages' );
+       }
+
+       public function execute( $par ) {
+               $context = new DerivativeContext( $this->getContext() );
+
+               $this->setHeaders();
+               $this->outputHeader();
+               $mimeAnalyzer = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
+               $this->mediaTypes = $mimeAnalyzer->getMediaTypes();
+
+               $out = $this->getOutput();
+               $this->addHelpLink( 'Help:New images' );
+
+               $opts = new FormOptions();
+
+               $opts->add( 'like', '' );
+               $opts->add( 'user', '' );
+               $opts->add( 'showbots', false );
+               $opts->add( 'hidepatrolled', false );
+               $opts->add( 'mediatype', $this->mediaTypes );
+               $opts->add( 'limit', 50 );
+               $opts->add( 'offset', '' );
+               $opts->add( 'start', '' );
+               $opts->add( 'end', '' );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+
+               if ( $par !== null ) {
+                       $opts->setValue( is_numeric( $par ) ? 'limit' : 'like', $par );
+               }
+
+               // If start date comes after end date chronologically, swap them.
+               // They are swapped in the interface by JS.
+               $start = $opts->getValue( 'start' );
+               $end = $opts->getValue( 'end' );
+               if ( $start !== '' && $end !== '' && $start > $end ) {
+                       $temp = $end;
+                       $end = $start;
+                       $start = $temp;
+
+                       $opts->setValue( 'start', $start, true );
+                       $opts->setValue( 'end', $end, true );
+
+                       // also swap values in request object, which is used by HTMLForm
+                       // to pre-populate the fields with the previous input
+                       $request = $context->getRequest();
+                       $context->setRequest( new DerivativeRequest(
+                               $request,
+                               [ 'start' => $start, 'end' => $end ] + $request->getValues(),
+                               $request->wasPosted()
+                       ) );
+               }
+
+               // if all media types have been selected, wipe out the array to prevent
+               // the pointless IN(...) query condition (which would have no effect
+               // because every possible type has been selected)
+               $missingMediaTypes = array_diff( $this->mediaTypes, $opts->getValue( 'mediatype' ) );
+               if ( empty( $missingMediaTypes ) ) {
+                       $opts->setValue( 'mediatype', [] );
+               }
+
+               $opts->validateIntBounds( 'limit', 0, 500 );
+
+               $this->opts = $opts;
+
+               if ( !$this->including() ) {
+                       $this->setTopText();
+                       $this->buildForm( $context );
+               }
+
+               $pager = new NewFilesPager( $context, $opts, $this->getLinkRenderer() );
+
+               $out->addHTML( $pager->getBody() );
+               if ( !$this->including() ) {
+                       $out->addHTML( $pager->getNavigationBar() );
+               }
+       }
+
+       protected function buildForm( IContextSource $context ) {
+               $mediaTypesText = array_map( function ( $type ) {
+                       // mediastatistics-header-unknown, mediastatistics-header-bitmap,
+                       // mediastatistics-header-drawing, mediastatistics-header-audio,
+                       // mediastatistics-header-video, mediastatistics-header-multimedia,
+                       // mediastatistics-header-office, mediastatistics-header-text,
+                       // mediastatistics-header-executable, mediastatistics-header-archive,
+                       // mediastatistics-header-3d,
+                       return $this->msg( 'mediastatistics-header-' . strtolower( $type ) )->text();
+               }, $this->mediaTypes );
+               $mediaTypesOptions = array_combine( $mediaTypesText, $this->mediaTypes );
+               ksort( $mediaTypesOptions );
+
+               $formDescriptor = [
+                       'like' => [
+                               'type' => 'text',
+                               'label-message' => 'newimages-label',
+                               'name' => 'like',
+                       ],
+
+                       'user' => [
+                               'class' => 'HTMLUserTextField',
+                               'label-message' => 'newimages-user',
+                               'name' => 'user',
+                       ],
+
+                       'showbots' => [
+                               'type' => 'check',
+                               'label-message' => 'newimages-showbots',
+                               'name' => 'showbots',
+                       ],
+
+                       'hidepatrolled' => [
+                               'type' => 'check',
+                               'label-message' => 'newimages-hidepatrolled',
+                               'name' => 'hidepatrolled',
+                       ],
+
+                       'mediatype' => [
+                               'type' => 'multiselect',
+                               'flatlist' => true,
+                               'name' => 'mediatype',
+                               'label-message' => 'newimages-mediatype',
+                               'options' => $mediaTypesOptions,
+                               'default' => $this->mediaTypes,
+                       ],
+
+                       'limit' => [
+                               'type' => 'hidden',
+                               'default' => $this->opts->getValue( 'limit' ),
+                               'name' => 'limit',
+                       ],
+
+                       'offset' => [
+                               'type' => 'hidden',
+                               'default' => $this->opts->getValue( 'offset' ),
+                               'name' => 'offset',
+                       ],
+
+                       'start' => [
+                               'type' => 'date',
+                               'label-message' => 'date-range-from',
+                               'name' => 'start',
+                       ],
+
+                       'end' => [
+                               'type' => 'date',
+                               'label-message' => 'date-range-to',
+                               'name' => 'end',
+                       ],
+               ];
+
+               if ( $this->getConfig()->get( 'MiserMode' ) ) {
+                       unset( $formDescriptor['like'] );
+               }
+
+               if ( !$this->getUser()->useFilePatrol() ) {
+                       unset( $formDescriptor['hidepatrolled'] );
+               }
+
+               HTMLForm::factory( 'ooui', $formDescriptor, $context )
+                       // For the 'multiselect' field values to be preserved on submit
+                       ->setFormIdentifier( 'specialnewimages' )
+                       ->setWrapperLegendMsg( 'newimages-legend' )
+                       ->setSubmitTextMsg( 'ilsubmit' )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
+       }
+
+       protected function getGroupName() {
+               return 'changes';
+       }
+
+       /**
+        * Send the text to be displayed above the options
+        */
+       function setTopText() {
+               $message = $this->msg( 'newimagestext' )->inContentLanguage();
+               if ( !$message->isDisabled() ) {
+                       $contLang = MediaWikiServices::getInstance()->getContentLanguage();
+                       $this->getOutput()->addWikiTextAsContent(
+                               Html::rawElement( 'div',
+                                       [
+
+                                               'lang' => $contLang->getHtmlCode(),
+                                               'dir' => $contLang->getDir()
+                                       ],
+                                       "\n" . $message->plain() . "\n"
+                               )
+                       );
+               }
+       }
+}
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
deleted file mode 100644 (file)
index 29e7789..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-<?php
-/**
- * Implements Special:Newimages
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-class SpecialNewFiles extends IncludableSpecialPage {
-       /** @var FormOptions */
-       protected $opts;
-
-       /** @var string[] */
-       protected $mediaTypes;
-
-       public function __construct() {
-               parent::__construct( 'Newimages' );
-       }
-
-       public function execute( $par ) {
-               $context = new DerivativeContext( $this->getContext() );
-
-               $this->setHeaders();
-               $this->outputHeader();
-               $mimeAnalyzer = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
-               $this->mediaTypes = $mimeAnalyzer->getMediaTypes();
-
-               $out = $this->getOutput();
-               $this->addHelpLink( 'Help:New images' );
-
-               $opts = new FormOptions();
-
-               $opts->add( 'like', '' );
-               $opts->add( 'user', '' );
-               $opts->add( 'showbots', false );
-               $opts->add( 'hidepatrolled', false );
-               $opts->add( 'mediatype', $this->mediaTypes );
-               $opts->add( 'limit', 50 );
-               $opts->add( 'offset', '' );
-               $opts->add( 'start', '' );
-               $opts->add( 'end', '' );
-
-               $opts->fetchValuesFromRequest( $this->getRequest() );
-
-               if ( $par !== null ) {
-                       $opts->setValue( is_numeric( $par ) ? 'limit' : 'like', $par );
-               }
-
-               // If start date comes after end date chronologically, swap them.
-               // They are swapped in the interface by JS.
-               $start = $opts->getValue( 'start' );
-               $end = $opts->getValue( 'end' );
-               if ( $start !== '' && $end !== '' && $start > $end ) {
-                       $temp = $end;
-                       $end = $start;
-                       $start = $temp;
-
-                       $opts->setValue( 'start', $start, true );
-                       $opts->setValue( 'end', $end, true );
-
-                       // also swap values in request object, which is used by HTMLForm
-                       // to pre-populate the fields with the previous input
-                       $request = $context->getRequest();
-                       $context->setRequest( new DerivativeRequest(
-                               $request,
-                               [ 'start' => $start, 'end' => $end ] + $request->getValues(),
-                               $request->wasPosted()
-                       ) );
-               }
-
-               // if all media types have been selected, wipe out the array to prevent
-               // the pointless IN(...) query condition (which would have no effect
-               // because every possible type has been selected)
-               $missingMediaTypes = array_diff( $this->mediaTypes, $opts->getValue( 'mediatype' ) );
-               if ( empty( $missingMediaTypes ) ) {
-                       $opts->setValue( 'mediatype', [] );
-               }
-
-               $opts->validateIntBounds( 'limit', 0, 500 );
-
-               $this->opts = $opts;
-
-               if ( !$this->including() ) {
-                       $this->setTopText();
-                       $this->buildForm( $context );
-               }
-
-               $pager = new NewFilesPager( $context, $opts, $this->getLinkRenderer() );
-
-               $out->addHTML( $pager->getBody() );
-               if ( !$this->including() ) {
-                       $out->addHTML( $pager->getNavigationBar() );
-               }
-       }
-
-       protected function buildForm( IContextSource $context ) {
-               $mediaTypesText = array_map( function ( $type ) {
-                       // mediastatistics-header-unknown, mediastatistics-header-bitmap,
-                       // mediastatistics-header-drawing, mediastatistics-header-audio,
-                       // mediastatistics-header-video, mediastatistics-header-multimedia,
-                       // mediastatistics-header-office, mediastatistics-header-text,
-                       // mediastatistics-header-executable, mediastatistics-header-archive,
-                       // mediastatistics-header-3d,
-                       return $this->msg( 'mediastatistics-header-' . strtolower( $type ) )->text();
-               }, $this->mediaTypes );
-               $mediaTypesOptions = array_combine( $mediaTypesText, $this->mediaTypes );
-               ksort( $mediaTypesOptions );
-
-               $formDescriptor = [
-                       'like' => [
-                               'type' => 'text',
-                               'label-message' => 'newimages-label',
-                               'name' => 'like',
-                       ],
-
-                       'user' => [
-                               'class' => 'HTMLUserTextField',
-                               'label-message' => 'newimages-user',
-                               'name' => 'user',
-                       ],
-
-                       'showbots' => [
-                               'type' => 'check',
-                               'label-message' => 'newimages-showbots',
-                               'name' => 'showbots',
-                       ],
-
-                       'hidepatrolled' => [
-                               'type' => 'check',
-                               'label-message' => 'newimages-hidepatrolled',
-                               'name' => 'hidepatrolled',
-                       ],
-
-                       'mediatype' => [
-                               'type' => 'multiselect',
-                               'flatlist' => true,
-                               'name' => 'mediatype',
-                               'label-message' => 'newimages-mediatype',
-                               'options' => $mediaTypesOptions,
-                               'default' => $this->mediaTypes,
-                       ],
-
-                       'limit' => [
-                               'type' => 'hidden',
-                               'default' => $this->opts->getValue( 'limit' ),
-                               'name' => 'limit',
-                       ],
-
-                       'offset' => [
-                               'type' => 'hidden',
-                               'default' => $this->opts->getValue( 'offset' ),
-                               'name' => 'offset',
-                       ],
-
-                       'start' => [
-                               'type' => 'date',
-                               'label-message' => 'date-range-from',
-                               'name' => 'start',
-                       ],
-
-                       'end' => [
-                               'type' => 'date',
-                               'label-message' => 'date-range-to',
-                               'name' => 'end',
-                       ],
-               ];
-
-               if ( $this->getConfig()->get( 'MiserMode' ) ) {
-                       unset( $formDescriptor['like'] );
-               }
-
-               if ( !$this->getUser()->useFilePatrol() ) {
-                       unset( $formDescriptor['hidepatrolled'] );
-               }
-
-               HTMLForm::factory( 'ooui', $formDescriptor, $context )
-                       // For the 'multiselect' field values to be preserved on submit
-                       ->setFormIdentifier( 'specialnewimages' )
-                       ->setWrapperLegendMsg( 'newimages-legend' )
-                       ->setSubmitTextMsg( 'ilsubmit' )
-                       ->setMethod( 'get' )
-                       ->prepareForm()
-                       ->displayForm( false );
-       }
-
-       protected function getGroupName() {
-               return 'changes';
-       }
-
-       /**
-        * Send the text to be displayed above the options
-        */
-       function setTopText() {
-               $message = $this->msg( 'newimagestext' )->inContentLanguage();
-               if ( !$message->isDisabled() ) {
-                       $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-                       $this->getOutput()->addWikiTextAsContent(
-                               Html::rawElement( 'div',
-                                       [
-
-                                               'lang' => $contLang->getHtmlCode(),
-                                               'dir' => $contLang->getDir()
-                                       ],
-                                       "\n" . $message->plain() . "\n"
-                               )
-                       );
-               }
-       }
-}
index 978efa7..df8bce1 100644 (file)
@@ -25,7 +25,6 @@
  * The web server should generally be configured to make this accessible via a canonical URL/URI,
  * such as <http://my.domain.org/data/main/Foo>.
  *
- * @class
  * @ingroup SpecialPage
  */
 class SpecialPageData extends SpecialPage {
index 8865654..26f3665 100644 (file)
@@ -229,13 +229,7 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
                        $sql = $dbr->limitResult( $sql, $limit, false );
                }
 
-               $res = $dbr->query( $sql, __METHOD__ );
-
-               if ( $res->numRows() == 0 ) {
-                       $this->mResultEmpty = true;
-               }
-
-               return $res;
+               return $dbr->query( $sql, __METHOD__ );
        }
 
        function setTopText( FormOptions $opts ) {
index 50867dd..82d8b73 100644 (file)
@@ -90,7 +90,9 @@ class SpecialRedirect extends FormSpecialPage {
                }
                $userpage = Title::makeTitle( NS_USER, $username );
 
-               return Status::newGood( $userpage->getFullURL( '', false, PROTO_CURRENT ) );
+               return Status::newGood( [
+                       $userpage->getFullURL( '', false, PROTO_CURRENT ), 302
+               ] );
        }
 
        /**
diff --git a/includes/specials/SpecialShortPages.php b/includes/specials/SpecialShortPages.php
new file mode 100644 (file)
index 0000000..f1bd065
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Implements Special:Shortpages
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * SpecialShortpages extends QueryPage. It is used to return the shortest
+ * pages in the database.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialShortPages extends QueryPage {
+
+       function __construct( $name = 'Shortpages' ) {
+               parent::__construct( $name );
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               $config = $this->getConfig();
+               $blacklist = $config->get( 'ShortPagesNamespaceBlacklist' );
+               $tables = [ 'page' ];
+               $conds = [
+                       'page_namespace' => array_diff(
+                               MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
+                               $blacklist
+                       ),
+                       'page_is_redirect' => 0
+               ];
+               $joinConds = [];
+               $options = [ 'USE INDEX' => [ 'page' => 'page_redirect_namespace_len' ] ];
+
+               // Allow extensions to modify the query
+               Hooks::run( 'ShortPagesQuery', [ &$tables, &$conds, &$joinConds, &$options ] );
+
+               return [
+                       'tables' => $tables,
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_len'
+                       ],
+                       'conds' => $conds,
+                       'join_conds' => $joinConds,
+                       'options' => $options
+               ];
+       }
+
+       public function reallyDoQuery( $limit, $offset = false ) {
+               $fname = static::class . '::reallyDoQuery';
+               $dbr = $this->getRecacheDB();
+               $query = $this->getQueryInfo();
+               $order = $this->getOrderFields();
+
+               if ( $this->sortDescending() ) {
+                       foreach ( $order as &$field ) {
+                               $field .= ' DESC';
+                       }
+               }
+
+               $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
+               $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
+               $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
+               $options = isset( $query['options'] ) ? (array)$query['options'] : [];
+               $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
+
+               if ( $limit !== false ) {
+                       $options['LIMIT'] = intval( $limit );
+               }
+
+               if ( $offset !== false ) {
+                       $options['OFFSET'] = intval( $offset );
+               }
+
+               $namespaces = $conds['page_namespace'];
+               if ( count( $namespaces ) === 1 ) {
+                       $options['ORDER BY'] = $order;
+                       $res = $dbr->select( $tables, $fields, $conds, $fname,
+                               $options, $join_conds
+                       );
+               } else {
+                       unset( $conds['page_namespace'] );
+                       $options['INNER ORDER BY'] = $order;
+                       $options['ORDER BY'] = [ 'value' . ( $this->sortDescending() ? ' DESC' : '' ) ];
+                       $sql = $dbr->unionConditionPermutations(
+                               $tables,
+                               $fields,
+                               [ 'page_namespace' => $namespaces ],
+                               $conds,
+                               $fname,
+                               $options,
+                               $join_conds
+                       );
+                       $res = $dbr->query( $sql, $fname );
+               }
+
+               return $res;
+       }
+
+       function getOrderFields() {
+               return [ 'page_len' ];
+       }
+
+       /**
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $dm = $this->getLanguage()->getDirMark();
+
+               $title = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$title ) {
+                       return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+               }
+
+               $linkRenderer = $this->getLinkRenderer();
+               $hlink = $linkRenderer->makeKnownLink(
+                       $title,
+                       $this->msg( 'hist' )->text(),
+                       [],
+                       [ 'action' => 'history' ]
+               );
+               $hlinkInParentheses = $this->msg( 'parentheses' )->rawParams( $hlink )->escaped();
+
+               if ( $this->isCached() ) {
+                       $plink = $linkRenderer->makeLink( $title );
+                       $exists = $title->exists();
+               } else {
+                       $plink = $linkRenderer->makeKnownLink( $title );
+                       $exists = true;
+               }
+
+               $size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
+
+               return $exists
+                       ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
+                       : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
deleted file mode 100644 (file)
index 94da25d..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php
-/**
- * Implements Special:Shortpages
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * SpecialShortpages extends QueryPage. It is used to return the shortest
- * pages in the database.
- *
- * @ingroup SpecialPage
- */
-class ShortPagesPage extends QueryPage {
-
-       function __construct( $name = 'Shortpages' ) {
-               parent::__construct( $name );
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               $config = $this->getConfig();
-               $blacklist = $config->get( 'ShortPagesNamespaceBlacklist' );
-               $tables = [ 'page' ];
-               $conds = [
-                       'page_namespace' => array_diff(
-                               MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(),
-                               $blacklist
-                       ),
-                       'page_is_redirect' => 0
-               ];
-               $joinConds = [];
-               $options = [ 'USE INDEX' => [ 'page' => 'page_redirect_namespace_len' ] ];
-
-               // Allow extensions to modify the query
-               Hooks::run( 'ShortPagesQuery', [ &$tables, &$conds, &$joinConds, &$options ] );
-
-               return [
-                       'tables' => $tables,
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_len'
-                       ],
-                       'conds' => $conds,
-                       'join_conds' => $joinConds,
-                       'options' => $options
-               ];
-       }
-
-       public function reallyDoQuery( $limit, $offset = false ) {
-               $fname = static::class . '::reallyDoQuery';
-               $dbr = $this->getRecacheDB();
-               $query = $this->getQueryInfo();
-               $order = $this->getOrderFields();
-
-               if ( $this->sortDescending() ) {
-                       foreach ( $order as &$field ) {
-                               $field .= ' DESC';
-                       }
-               }
-
-               $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
-               $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
-               $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
-               $options = isset( $query['options'] ) ? (array)$query['options'] : [];
-               $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
-
-               if ( $limit !== false ) {
-                       $options['LIMIT'] = intval( $limit );
-               }
-
-               if ( $offset !== false ) {
-                       $options['OFFSET'] = intval( $offset );
-               }
-
-               $namespaces = $conds['page_namespace'];
-               if ( count( $namespaces ) === 1 ) {
-                       $options['ORDER BY'] = $order;
-                       $res = $dbr->select( $tables, $fields, $conds, $fname,
-                               $options, $join_conds
-                       );
-               } else {
-                       unset( $conds['page_namespace'] );
-                       $options['INNER ORDER BY'] = $order;
-                       $options['ORDER BY'] = [ 'value' . ( $this->sortDescending() ? ' DESC' : '' ) ];
-                       $sql = $dbr->unionConditionPermutations(
-                               $tables,
-                               $fields,
-                               [ 'page_namespace' => $namespaces ],
-                               $conds,
-                               $fname,
-                               $options,
-                               $join_conds
-                       );
-                       $res = $dbr->query( $sql, $fname );
-               }
-
-               return $res;
-       }
-
-       function getOrderFields() {
-               return [ 'page_len' ];
-       }
-
-       /**
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $dm = $this->getLanguage()->getDirMark();
-
-               $title = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$title ) {
-                       return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
-               }
-
-               $linkRenderer = $this->getLinkRenderer();
-               $hlink = $linkRenderer->makeKnownLink(
-                       $title,
-                       $this->msg( 'hist' )->text(),
-                       [],
-                       [ 'action' => 'history' ]
-               );
-               $hlinkInParentheses = $this->msg( 'parentheses' )->rawParams( $hlink )->escaped();
-
-               if ( $this->isCached() ) {
-                       $plink = $linkRenderer->makeLink( $title );
-                       $exists = $title->exists();
-               } else {
-                       $plink = $linkRenderer->makeKnownLink( $title );
-                       $exists = true;
-               }
-
-               $size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
-
-               return $exists
-                       ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
-                       : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
index eff8889..83153ae 100644 (file)
@@ -47,7 +47,6 @@ class SpecialStatistics extends SpecialPage {
                $this->total = SiteStats::pages();
                $this->users = SiteStats::users();
                $this->activeUsers = SiteStats::activeUsers();
-               $this->hook = '';
 
                $text = Xml::openElement( 'table', [ 'class' => 'wikitable mw-statistics-table' ] );
 
diff --git a/includes/specials/SpecialUncategorizedCategories.php b/includes/specials/SpecialUncategorizedCategories.php
new file mode 100644 (file)
index 0000000..7349d95
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Implements Special:Uncategorizedcategories
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * A special page that lists uncategorized categories
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUncategorizedCategories extends SpecialUncategorizedPages {
+       /**
+        * Holds a list of categories, which shouldn't be listed on this special page,
+        * even if it is uncategorized.
+        * @var array
+        */
+       private $exceptionList = null;
+
+       function __construct( $name = 'Uncategorizedcategories' ) {
+               parent::__construct( $name );
+               $this->requestedNamespace = NS_CATEGORY;
+       }
+
+       /**
+        * Returns an array of category titles (usually without the namespace), which
+        * shouldn't be listed on this page, even if they're uncategorized.
+        *
+        * @return array
+        */
+       private function getExceptionList() {
+               if ( $this->exceptionList === null ) {
+                       $this->exceptionList = [];
+                       $exList = $this->msg( 'uncategorized-categories-exceptionlist' )
+                               ->inContentLanguage()->plain();
+                       $proposedTitles = explode( "\n", $exList );
+                       foreach ( $proposedTitles as $count => $titleStr ) {
+                               if ( strpos( $titleStr, '*' ) !== 0 ) {
+                                       continue;
+                               }
+                               $titleStr = preg_replace( "/^\\*\\s*/", '', $titleStr );
+                               $title = Title::newFromText( $titleStr, NS_CATEGORY );
+                               if ( $title && $title->getNamespace() !== NS_CATEGORY ) {
+                                       $title = Title::makeTitleSafe( NS_CATEGORY, $titleStr );
+                               }
+                               if ( $title ) {
+                                       $this->exceptionList[] = $title->getDBkey();
+                               }
+                       }
+               }
+               return $this->exceptionList;
+       }
+
+       public function getQueryInfo() {
+               $dbr = wfGetDB( DB_REPLICA );
+               $query = parent::getQueryInfo();
+               $exceptionList = $this->getExceptionList();
+               if ( $exceptionList ) {
+                       $query['conds'][] = 'page_title not in ( ' . $dbr->makeList( $exceptionList ) . ' )';
+               }
+
+               return $query;
+       }
+
+       /**
+        * Formats the result
+        * @param Skin $skin The current skin
+        * @param object $result The query result
+        * @return string The category link
+        */
+       function formatResult( $skin, $result ) {
+               $title = Title::makeTitle( NS_CATEGORY, $result->title );
+               $text = $title->getText();
+
+               return $this->getLinkRenderer()->makeKnownLink( $title, $text );
+       }
+}
diff --git a/includes/specials/SpecialUncategorizedImages.php b/includes/specials/SpecialUncategorizedImages.php
new file mode 100644 (file)
index 0000000..9dcd1bd
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Implements Special:Uncategorizedimages
+ *
+ * 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 SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * Special page lists images which haven't been categorised
+ *
+ * @ingroup SpecialPage
+ * @todo FIXME: Use an instance of UncategorizedPagesPage or something
+ */
+class SpecialUncategorizedImages extends ImageQueryPage {
+       function __construct( $name = 'Uncategorizedimages' ) {
+               parent::__construct( $name );
+               $this->addHelpLink( 'Help:Categories' );
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function getQueryInfo() {
+               return [
+                       'tables' => [ 'page', 'categorylinks' ],
+                       'fields' => [ 'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title' ],
+                       'conds' => [ 'cl_from IS NULL',
+                               'page_namespace' => NS_FILE,
+                               'page_is_redirect' => 0 ],
+                       'join_conds' => [ 'categorylinks' => [
+                               'LEFT JOIN', 'cl_from=page_id' ] ]
+               ];
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialUncategorizedPages.php b/includes/specials/SpecialUncategorizedPages.php
new file mode 100644 (file)
index 0000000..3610c01
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Implements Special:Uncategorizedpages
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A special page looking for page without any category.
+ *
+ * @ingroup SpecialPage
+ * @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
+ */
+class SpecialUncategorizedPages extends PageQueryPage {
+       /** @var int|false */
+       protected $requestedNamespace = false;
+
+       function __construct( $name = 'Uncategorizedpages' ) {
+               parent::__construct( $name );
+               $this->addHelpLink( 'Help:Categories' );
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function getQueryInfo() {
+               return [
+                       'tables' => [ 'page', 'categorylinks' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title'
+                       ],
+                       // default for page_namespace is all content namespaces (if requestedNamespace is false)
+                       // otherwise, page_namespace is requestedNamespace
+                       'conds' => [
+                               'cl_from IS NULL',
+                               'page_namespace' => $this->requestedNamespace !== false
+                                               ? $this->requestedNamespace
+                                               : MediaWikiServices::getInstance()->getNamespaceInfo()->
+                                                       getContentNamespaces(),
+                               'page_is_redirect' => 0
+                       ],
+                       'join_conds' => [
+                               'categorylinks' => [ 'LEFT JOIN', 'cl_from = page_id' ]
+                       ]
+               ];
+       }
+
+       function getOrderFields() {
+               // For some crazy reason ordering by a constant
+               // causes a filesort
+               if ( $this->requestedNamespace === false &&
+                       count( MediaWikiServices::getInstance()->getNamespaceInfo()->
+                               getContentNamespaces() ) > 1
+               ) {
+                       return [ 'page_namespace', 'page_title' ];
+               }
+
+               return [ 'page_title' ];
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialUncategorizedTemplates.php b/includes/specials/SpecialUncategorizedTemplates.php
new file mode 100644 (file)
index 0000000..3d6d383
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Implements Special:Uncategorizedtemplates
+ *
+ * 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 SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * Special page lists all uncategorised pages in the
+ * template namespace
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUncategorizedTemplates extends SpecialUncategorizedPages {
+       public function __construct( $name = 'Uncategorizedtemplates' ) {
+               parent::__construct( $name );
+               $this->requestedNamespace = NS_TEMPLATE;
+       }
+}
diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php
deleted file mode 100644 (file)
index 60cbff1..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-/**
- * Implements Special:Uncategorizedcategories
- *
- * 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 SpecialPage
- */
-
-/**
- * A special page that lists uncategorized categories
- *
- * @ingroup SpecialPage
- */
-class UncategorizedCategoriesPage extends UncategorizedPagesPage {
-       /**
-        * Holds a list of categories, which shouldn't be listed on this special page,
-        * even if it is uncategorized.
-        * @var array
-        */
-       private $exceptionList = null;
-
-       function __construct( $name = 'Uncategorizedcategories' ) {
-               parent::__construct( $name );
-               $this->requestedNamespace = NS_CATEGORY;
-       }
-
-       /**
-        * Returns an array of category titles (usually without the namespace), which
-        * shouldn't be listed on this page, even if they're uncategorized.
-        *
-        * @return array
-        */
-       private function getExceptionList() {
-               if ( $this->exceptionList === null ) {
-                       $this->exceptionList = [];
-                       $exList = $this->msg( 'uncategorized-categories-exceptionlist' )
-                               ->inContentLanguage()->plain();
-                       $proposedTitles = explode( "\n", $exList );
-                       foreach ( $proposedTitles as $count => $titleStr ) {
-                               if ( strpos( $titleStr, '*' ) !== 0 ) {
-                                       continue;
-                               }
-                               $titleStr = preg_replace( "/^\\*\\s*/", '', $titleStr );
-                               $title = Title::newFromText( $titleStr, NS_CATEGORY );
-                               if ( $title && $title->getNamespace() !== NS_CATEGORY ) {
-                                       $title = Title::makeTitleSafe( NS_CATEGORY, $titleStr );
-                               }
-                               if ( $title ) {
-                                       $this->exceptionList[] = $title->getDBkey();
-                               }
-                       }
-               }
-               return $this->exceptionList;
-       }
-
-       public function getQueryInfo() {
-               $dbr = wfGetDB( DB_REPLICA );
-               $query = parent::getQueryInfo();
-               $exceptionList = $this->getExceptionList();
-               if ( $exceptionList ) {
-                       $query['conds'][] = 'page_title not in ( ' . $dbr->makeList( $exceptionList ) . ' )';
-               }
-
-               return $query;
-       }
-
-       /**
-        * Formats the result
-        * @param Skin $skin The current skin
-        * @param object $result The query result
-        * @return string The category link
-        */
-       function formatResult( $skin, $result ) {
-               $title = Title::makeTitle( NS_CATEGORY, $result->title );
-               $text = $title->getText();
-
-               return $this->getLinkRenderer()->makeKnownLink( $title, $text );
-       }
-}
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
deleted file mode 100644 (file)
index ed2d5cf..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-/**
- * Implements Special:Uncategorizedimages
- *
- * 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 SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * Special page lists images which haven't been categorised
- *
- * @ingroup SpecialPage
- * @todo FIXME: Use an instance of UncategorizedPagesPage or something
- */
-class UncategorizedImagesPage extends ImageQueryPage {
-       function __construct( $name = 'Uncategorizedimages' ) {
-               parent::__construct( $name );
-               $this->addHelpLink( 'Help:Categories' );
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function getQueryInfo() {
-               return [
-                       'tables' => [ 'page', 'categorylinks' ],
-                       'fields' => [ 'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title' ],
-                       'conds' => [ 'cl_from IS NULL',
-                               'page_namespace' => NS_FILE,
-                               'page_is_redirect' => 0 ],
-                       'join_conds' => [ 'categorylinks' => [
-                               'LEFT JOIN', 'cl_from=page_id' ] ]
-               ];
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
deleted file mode 100644 (file)
index 0b7da7b..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-/**
- * Implements Special:Uncategorizedpages
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A special page looking for page without any category.
- *
- * @ingroup SpecialPage
- * @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
- */
-class UncategorizedPagesPage extends PageQueryPage {
-       /** @var int|false */
-       protected $requestedNamespace = false;
-
-       function __construct( $name = 'Uncategorizedpages' ) {
-               parent::__construct( $name );
-               $this->addHelpLink( 'Help:Categories' );
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function getQueryInfo() {
-               return [
-                       'tables' => [ 'page', 'categorylinks' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title'
-                       ],
-                       // default for page_namespace is all content namespaces (if requestedNamespace is false)
-                       // otherwise, page_namespace is requestedNamespace
-                       'conds' => [
-                               'cl_from IS NULL',
-                               'page_namespace' => $this->requestedNamespace !== false
-                                               ? $this->requestedNamespace
-                                               : MediaWikiServices::getInstance()->getNamespaceInfo()->
-                                                       getContentNamespaces(),
-                               'page_is_redirect' => 0
-                       ],
-                       'join_conds' => [
-                               'categorylinks' => [ 'LEFT JOIN', 'cl_from = page_id' ]
-                       ]
-               ];
-       }
-
-       function getOrderFields() {
-               // For some crazy reason ordering by a constant
-               // causes a filesort
-               if ( $this->requestedNamespace === false &&
-                       count( MediaWikiServices::getInstance()->getNamespaceInfo()->
-                               getContentNamespaces() ) > 1
-               ) {
-                       return [ 'page_namespace', 'page_title' ];
-               }
-
-               return [ 'page_title' ];
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php
deleted file mode 100644 (file)
index af038fa..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-/**
- * Implements Special:Uncategorizedtemplates
- *
- * 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 SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * Special page lists all uncategorised pages in the
- * template namespace
- *
- * @ingroup SpecialPage
- */
-class UncategorizedTemplatesPage extends UncategorizedPagesPage {
-       public function __construct( $name = 'Uncategorizedtemplates' ) {
-               parent::__construct( $name );
-               $this->requestedNamespace = NS_TEMPLATE;
-       }
-}
index 075b5df..fe629db 100644 (file)
@@ -33,18 +33,28 @@ use Wikimedia\Rdbms\IResultWrapper;
  * @ingroup SpecialPage
  */
 class SpecialUndelete extends SpecialPage {
-       private $mAction;
-       private $mTarget;
-       private $mTimestamp;
-       private $mRestore;
-       private $mRevdel;
-       private $mInvert;
-       private $mFilename;
-       private $mTargetTimestamp;
-       private $mAllowed;
-       private $mCanView;
-       private $mComment;
-       private $mToken;
+       private $mAction;
+       private $mTarget;
+       private $mTimestamp;
+       private $mRestore;
+       private $mRevdel;
+       private $mInvert;
+       private $mFilename;
+       private $mTargetTimestamp;
+       private $mAllowed;
+       private $mCanView;
+       private $mComment;
+       private $mToken;
+       /** @var bool|null */
+       private $mPreview;
+       /** @var bool|null */
+       private $mDiff;
+       /** @var bool|null */
+       private $mDiffOnly;
+       /** @var bool|null */
+       private $mUnsuppress;
+       /** @var int[]|null */
+       private $mFileVersions;
 
        /** @var Title */
        private $mTargetObj;
diff --git a/includes/specials/SpecialUnusedCategories.php b/includes/specials/SpecialUnusedCategories.php
new file mode 100644 (file)
index 0000000..36367d2
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Implements Special:Unusedcategories
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * @ingroup SpecialPage
+ */
+class SpecialUnusedCategories extends QueryPage {
+       function __construct( $name = 'Unusedcategories' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function getPageHeader() {
+               return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'page', 'categorylinks', 'page_props' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title'
+                       ],
+                       'conds' => [
+                               'cl_from IS NULL',
+                               'page_namespace' => NS_CATEGORY,
+                               'page_is_redirect' => 0,
+                               'pp_page IS NULL'
+                       ],
+                       'join_conds' => [
+                               'categorylinks' => [ 'LEFT JOIN', 'cl_to = page_title' ],
+                               'page_props' => [ 'LEFT JOIN', [
+                                       'page_id = pp_page',
+                                       'pp_propname' => 'expectunusedcategory'
+                               ] ]
+                       ]
+               ];
+       }
+
+       /**
+        * A should come before Z (T32907)
+        * @return bool
+        */
+       function sortDescending() {
+               return false;
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $title = Title::makeTitle( NS_CATEGORY, $result->title );
+
+               return $this->getLinkRenderer()->makeLink( $title, $title->getText() );
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+
+       public function preprocessResults( $db, $res ) {
+               $this->executeLBFromResultWrapper( $res );
+       }
+}
diff --git a/includes/specials/SpecialUnusedImages.php b/includes/specials/SpecialUnusedImages.php
new file mode 100644 (file)
index 0000000..cb215e3
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Implements Special:Unusedimages
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * A special page that lists unused images
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnusedImages extends ImageQueryPage {
+       function __construct( $name = 'Unusedimages' ) {
+               parent::__construct( $name );
+       }
+
+       function isExpensive() {
+               return true;
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function getQueryInfo() {
+               $retval = [
+                       'tables' => [ 'image', 'imagelinks' ],
+                       'fields' => [
+                               'namespace' => NS_FILE,
+                               'title' => 'img_name',
+                               'value' => 'img_timestamp',
+                       ],
+                       'conds' => [ 'il_to IS NULL' ],
+                       'join_conds' => [ 'imagelinks' => [ 'LEFT JOIN', 'il_to = img_name' ] ]
+               ];
+
+               if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
+                       // Order is significant
+                       $retval['tables'] = [ 'image', 'page', 'categorylinks',
+                               'imagelinks' ];
+                       $retval['conds']['page_namespace'] = NS_FILE;
+                       $retval['conds'][] = 'cl_from IS NULL';
+                       $retval['conds'][] = 'img_name = page_title';
+                       $retval['join_conds']['categorylinks'] = [
+                               'LEFT JOIN', 'cl_from = page_id' ];
+                       $retval['join_conds']['imagelinks'] = [
+                               'LEFT JOIN', 'il_to = page_title' ];
+               }
+
+               return $retval;
+       }
+
+       function usesTimestamps() {
+               return true;
+       }
+
+       function getPageHeader() {
+               if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
+                       return $this->msg(
+                               'unusedimagestext-categorizedimgisused'
+                       )->parseAsBlock();
+               }
+               return $this->msg( 'unusedimagestext' )->parseAsBlock();
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialUnusedTemplates.php b/includes/specials/SpecialUnusedTemplates.php
new file mode 100644 (file)
index 0000000..119ef87
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Implements Special:Unusedtemplates
+ *
+ * Copyright © 2006 Rob Church
+ *
+ * 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 SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * A special page that lists unused templates
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnusedTemplates extends QueryPage {
+       function __construct( $name = 'Unusedtemplates' ) {
+               parent::__construct( $name );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       public function getQueryInfo() {
+               return [
+                       'tables' => [ 'page', 'templatelinks' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title'
+                       ],
+                       'conds' => [
+                               'page_namespace' => NS_TEMPLATE,
+                               'tl_from IS NULL',
+                               'page_is_redirect' => 0
+                       ],
+                       'join_conds' => [ 'templatelinks' => [
+                               'LEFT JOIN', [ 'tl_title = page_title',
+                                       'tl_namespace = page_namespace' ] ] ]
+               ];
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $linkRenderer = $this->getLinkRenderer();
+               $title = Title::makeTitle( NS_TEMPLATE, $result->title );
+               $pageLink = $linkRenderer->makeKnownLink(
+                       $title,
+                       null,
+                       [],
+                       [ 'redirect' => 'no' ]
+               );
+               $wlhLink = $linkRenderer->makeKnownLink(
+                       SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ),
+                       $this->msg( 'unusedtemplateswlh' )->text()
+               );
+
+               return $this->getLanguage()->specialList( $pageLink, $wlhLink );
+       }
+
+       function getPageHeader() {
+               return $this->msg( 'unusedtemplatestext' )->parseAsBlock();
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
deleted file mode 100644 (file)
index 2577a10..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-/**
- * Implements Special:Unusedcategories
- *
- * 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 SpecialPage
- */
-
-/**
- * @ingroup SpecialPage
- */
-class UnusedCategoriesPage extends QueryPage {
-       function __construct( $name = 'Unusedcategories' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function getPageHeader() {
-               return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'page', 'categorylinks', 'page_props' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title'
-                       ],
-                       'conds' => [
-                               'cl_from IS NULL',
-                               'page_namespace' => NS_CATEGORY,
-                               'page_is_redirect' => 0,
-                               'pp_page IS NULL'
-                       ],
-                       'join_conds' => [
-                               'categorylinks' => [ 'LEFT JOIN', 'cl_to = page_title' ],
-                               'page_props' => [ 'LEFT JOIN', [
-                                       'page_id = pp_page',
-                                       'pp_propname' => 'expectunusedcategory'
-                               ] ]
-                       ]
-               ];
-       }
-
-       /**
-        * A should come before Z (T32907)
-        * @return bool
-        */
-       function sortDescending() {
-               return false;
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $title = Title::makeTitle( NS_CATEGORY, $result->title );
-
-               return $this->getLinkRenderer()->makeLink( $title, $title->getText() );
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-
-       public function preprocessResults( $db, $res ) {
-               $this->executeLBFromResultWrapper( $res );
-       }
-}
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
deleted file mode 100644 (file)
index bcb3b85..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-/**
- * Implements Special:Unusedimages
- *
- * 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 SpecialPage
- */
-
-/**
- * A special page that lists unused images
- *
- * @ingroup SpecialPage
- */
-class UnusedimagesPage extends ImageQueryPage {
-       function __construct( $name = 'Unusedimages' ) {
-               parent::__construct( $name );
-       }
-
-       function isExpensive() {
-               return true;
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function getQueryInfo() {
-               $retval = [
-                       'tables' => [ 'image', 'imagelinks' ],
-                       'fields' => [
-                               'namespace' => NS_FILE,
-                               'title' => 'img_name',
-                               'value' => 'img_timestamp',
-                       ],
-                       'conds' => [ 'il_to IS NULL' ],
-                       'join_conds' => [ 'imagelinks' => [ 'LEFT JOIN', 'il_to = img_name' ] ]
-               ];
-
-               if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
-                       // Order is significant
-                       $retval['tables'] = [ 'image', 'page', 'categorylinks',
-                               'imagelinks' ];
-                       $retval['conds']['page_namespace'] = NS_FILE;
-                       $retval['conds'][] = 'cl_from IS NULL';
-                       $retval['conds'][] = 'img_name = page_title';
-                       $retval['join_conds']['categorylinks'] = [
-                               'LEFT JOIN', 'cl_from = page_id' ];
-                       $retval['join_conds']['imagelinks'] = [
-                               'LEFT JOIN', 'il_to = page_title' ];
-               }
-
-               return $retval;
-       }
-
-       function usesTimestamps() {
-               return true;
-       }
-
-       function getPageHeader() {
-               if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
-                       return $this->msg(
-                               'unusedimagestext-categorizedimgisused'
-                       )->parseAsBlock();
-               }
-               return $this->msg( 'unusedimagestext' )->parseAsBlock();
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
deleted file mode 100644 (file)
index f73be43..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-/**
- * Implements Special:Unusedtemplates
- *
- * Copyright © 2006 Rob Church
- *
- * 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 SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * A special page that lists unused templates
- *
- * @ingroup SpecialPage
- */
-class UnusedtemplatesPage extends QueryPage {
-       function __construct( $name = 'Unusedtemplates' ) {
-               parent::__construct( $name );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       public function getQueryInfo() {
-               return [
-                       'tables' => [ 'page', 'templatelinks' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title'
-                       ],
-                       'conds' => [
-                               'page_namespace' => NS_TEMPLATE,
-                               'tl_from IS NULL',
-                               'page_is_redirect' => 0
-                       ],
-                       'join_conds' => [ 'templatelinks' => [
-                               'LEFT JOIN', [ 'tl_title = page_title',
-                                       'tl_namespace = page_namespace' ] ] ]
-               ];
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $linkRenderer = $this->getLinkRenderer();
-               $title = Title::makeTitle( NS_TEMPLATE, $result->title );
-               $pageLink = $linkRenderer->makeKnownLink(
-                       $title,
-                       null,
-                       [],
-                       [ 'redirect' => 'no' ]
-               );
-               $wlhLink = $linkRenderer->makeKnownLink(
-                       SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ),
-                       $this->msg( 'unusedtemplateswlh' )->text()
-               );
-
-               return $this->getLanguage()->specialList( $pageLink, $wlhLink );
-       }
-
-       function getPageHeader() {
-               return $this->msg( 'unusedtemplatestext' )->parseAsBlock();
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialUnwatchedPages.php b/includes/specials/SpecialUnwatchedPages.php
new file mode 100644 (file)
index 0000000..42adaa0
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Implements Special:Unwatchedpages
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * 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 SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A special page that displays a list of pages that are not on anyones watchlist.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnwatchedPages extends QueryPage {
+
+       function __construct( $name = 'Unwatchedpages' ) {
+               parent::__construct( $name, 'unwatchedpages' );
+       }
+
+       public function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       /**
+        * Pre-cache page existence to speed up link generation
+        *
+        * @param IDatabase $db
+        * @param IResultWrapper $res
+        */
+       public function preprocessResults( $db, $res ) {
+               if ( !$res->numRows() ) {
+                       return;
+               }
+
+               $batch = new LinkBatch();
+               foreach ( $res as $row ) {
+                       $batch->add( $row->namespace, $row->title );
+               }
+               $batch->execute();
+
+               $res->seek( 0 );
+       }
+
+       public function getQueryInfo() {
+               $dbr = wfGetDB( DB_REPLICA );
+               return [
+                       'tables' => [ 'page', 'watchlist' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_namespace'
+                       ],
+                       'conds' => [
+                               'wl_title IS NULL',
+                               'page_is_redirect' => 0,
+                               'page_namespace != ' . $dbr->addQuotes( NS_MEDIAWIKI ),
+                       ],
+                       'join_conds' => [ 'watchlist' => [
+                               'LEFT JOIN', [ 'wl_title = page_title',
+                                       'wl_namespace = page_namespace' ] ] ]
+               ];
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function getOrderFields() {
+               return [ 'page_namespace', 'page_title' ];
+       }
+
+       /**
+        * Add the JS
+        * @param string|null $par
+        */
+       public function execute( $par ) {
+               parent::execute( $par );
+               $this->getOutput()->addModules( 'mediawiki.special.unwatchedPages' );
+               $this->addHelpLink( 'Help:Watchlist' );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $nt = Title::makeTitleSafe( $result->namespace, $result->title );
+               if ( !$nt ) {
+                       return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
+                               Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+               }
+
+               $text = MediaWikiServices::getInstance()->getContentLanguage()->
+                       convert( htmlspecialchars( $nt->getPrefixedText() ) );
+
+               $linkRenderer = $this->getLinkRenderer();
+
+               $plink = $linkRenderer->makeKnownLink( $nt, new HtmlArmor( $text ) );
+               $wlink = $linkRenderer->makeKnownLink(
+                       $nt,
+                       $this->msg( 'watch' )->text(),
+                       [ 'class' => 'mw-watch-link' ],
+                       [ 'action' => 'watch' ]
+               );
+
+               return $this->getLanguage()->specialList( $plink, $wlink );
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
deleted file mode 100644 (file)
index 3c5de64..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-/**
- * Implements Special:Unwatchedpages
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * 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 SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * A special page that displays a list of pages that are not on anyones watchlist.
- *
- * @ingroup SpecialPage
- */
-class UnwatchedpagesPage extends QueryPage {
-
-       function __construct( $name = 'Unwatchedpages' ) {
-               parent::__construct( $name, 'unwatchedpages' );
-       }
-
-       public function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       /**
-        * Pre-cache page existence to speed up link generation
-        *
-        * @param IDatabase $db
-        * @param IResultWrapper $res
-        */
-       public function preprocessResults( $db, $res ) {
-               if ( !$res->numRows() ) {
-                       return;
-               }
-
-               $batch = new LinkBatch();
-               foreach ( $res as $row ) {
-                       $batch->add( $row->namespace, $row->title );
-               }
-               $batch->execute();
-
-               $res->seek( 0 );
-       }
-
-       public function getQueryInfo() {
-               $dbr = wfGetDB( DB_REPLICA );
-               return [
-                       'tables' => [ 'page', 'watchlist' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_namespace'
-                       ],
-                       'conds' => [
-                               'wl_title IS NULL',
-                               'page_is_redirect' => 0,
-                               'page_namespace != ' . $dbr->addQuotes( NS_MEDIAWIKI ),
-                       ],
-                       'join_conds' => [ 'watchlist' => [
-                               'LEFT JOIN', [ 'wl_title = page_title',
-                                       'wl_namespace = page_namespace' ] ] ]
-               ];
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function getOrderFields() {
-               return [ 'page_namespace', 'page_title' ];
-       }
-
-       /**
-        * Add the JS
-        * @param string|null $par
-        */
-       public function execute( $par ) {
-               parent::execute( $par );
-               $this->getOutput()->addModules( 'mediawiki.special.unwatchedPages' );
-               $this->addHelpLink( 'Help:Watchlist' );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $nt = Title::makeTitleSafe( $result->namespace, $result->title );
-               if ( !$nt ) {
-                       return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
-                               Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
-               }
-
-               $text = MediaWikiServices::getInstance()->getContentLanguage()->
-                       convert( htmlspecialchars( $nt->getPrefixedText() ) );
-
-               $linkRenderer = $this->getLinkRenderer();
-
-               $plink = $linkRenderer->makeKnownLink( $nt, new HtmlArmor( $text ) );
-               $wlink = $linkRenderer->makeKnownLink(
-                       $nt,
-                       $this->msg( 'watch' )->text(),
-                       [ 'class' => 'mw-watch-link' ],
-                       [ 'action' => 'watch' ]
-               );
-
-               return $this->getLanguage()->specialList( $plink, $wlink );
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialWantedCategories.php b/includes/specials/SpecialWantedCategories.php
new file mode 100644 (file)
index 0000000..659650c
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Implements Special:Wantedcategories
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * 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 SpecialPage
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * A querypage to list the most wanted categories - implements Special:Wantedcategories
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWantedCategories extends WantedQueryPage {
+       private $currentCategoryCounts;
+
+       function __construct( $name = 'Wantedcategories' ) {
+               parent::__construct( $name );
+       }
+
+       function getQueryInfo() {
+               return [
+                       'tables' => [ 'categorylinks', 'page' ],
+                       'fields' => [
+                               'namespace' => NS_CATEGORY,
+                               'title' => 'cl_to',
+                               'value' => 'COUNT(*)'
+                       ],
+                       'conds' => [ 'page_title IS NULL' ],
+                       'options' => [ 'GROUP BY' => 'cl_to' ],
+                       'join_conds' => [ 'page' => [ 'LEFT JOIN',
+                               [ 'page_title = cl_to',
+                                       'page_namespace' => NS_CATEGORY ] ] ]
+               ];
+       }
+
+       function preprocessResults( $db, $res ) {
+               parent::preprocessResults( $db, $res );
+
+               $this->currentCategoryCounts = [];
+
+               if ( !$res->numRows() || !$this->isCached() ) {
+                       return;
+               }
+
+               // Fetch (hopefully) up-to-date numbers of pages in each category.
+               // This should be fast enough as we limit the list to a reasonable length.
+
+               $allCategories = [];
+               foreach ( $res as $row ) {
+                       $allCategories[] = $row->title;
+               }
+
+               $categoryRes = $db->select(
+                       'category',
+                       [ 'cat_title', 'cat_pages' ],
+                       [ 'cat_title' => $allCategories ],
+                       __METHOD__
+               );
+               foreach ( $categoryRes as $row ) {
+                       $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
+               }
+
+               // Back to start for display
+               $res->seek( 0 );
+       }
+
+       /**
+        * @param Skin $skin
+        * @param object $result Result row
+        * @return string
+        */
+       function formatResult( $skin, $result ) {
+               $nt = Title::makeTitle( $result->namespace, $result->title );
+               $text = new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()
+                       ->convert( htmlspecialchars( $nt->getText() ) ) );
+
+               if ( !$this->isCached() ) {
+                       // We can assume the freshest data
+                       $plink = $this->getLinkRenderer()->makeBrokenLink(
+                               $nt,
+                               $text
+                       );
+                       $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+               } else {
+                       $plink = $this->getLinkRenderer()->makeLink( $nt, $text );
+
+                       $currentValue = $this->currentCategoryCounts[$result->title] ?? 0;
+                       $cachedValue = intval( $result->value ); // T76910
+
+                       // If the category has been created or emptied since the list was refreshed, strike it
+                       if ( $nt->isKnown() || $currentValue === 0 ) {
+                               $plink = "<del>$plink</del>";
+                       }
+
+                       // Show the current number of category entries if it changed
+                       if ( $currentValue !== $cachedValue ) {
+                               $nlinks = $this->msg( 'nmemberschanged' )
+                                       ->numParams( $cachedValue, $currentValue )->escaped();
+                       } else {
+                               $nlinks = $this->msg( 'nmembers' )->numParams( $cachedValue )->escaped();
+                       }
+               }
+
+               return $this->getLanguage()->specialList( $plink, $nlinks );
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialWantedTemplates.php b/includes/specials/SpecialWantedTemplates.php
new file mode 100644 (file)
index 0000000..453f7b8
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Implements Special:Wantedtemplates
+ *
+ * Copyright © 2008, Danny B.
+ * Based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com>
+ *
+ * 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 SpecialPage
+ * @author Danny B.
+ */
+
+/**
+ * A querypage to list the most wanted templates
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWantedTemplates extends WantedQueryPage {
+       function __construct( $name = 'Wantedtemplates' ) {
+               parent::__construct( $name );
+       }
+
+       function getQueryInfo() {
+               return [
+                       'tables' => [ 'templatelinks', 'page' ],
+                       'fields' => [
+                               'namespace' => 'tl_namespace',
+                               'title' => 'tl_title',
+                               'value' => 'COUNT(*)'
+                       ],
+                       'conds' => [
+                               'page_title IS NULL',
+                               'tl_namespace' => NS_TEMPLATE
+                       ],
+                       'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ],
+                       'join_conds' => [ 'page' => [ 'LEFT JOIN',
+                               [ 'page_namespace = tl_namespace',
+                                       'page_title = tl_title' ] ] ]
+               ];
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
deleted file mode 100644 (file)
index 5c62298..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-/**
- * Implements Special:Wantedcategories
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * 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 SpecialPage
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * A querypage to list the most wanted categories - implements Special:Wantedcategories
- *
- * @ingroup SpecialPage
- */
-class WantedCategoriesPage extends WantedQueryPage {
-       private $currentCategoryCounts;
-
-       function __construct( $name = 'Wantedcategories' ) {
-               parent::__construct( $name );
-       }
-
-       function getQueryInfo() {
-               return [
-                       'tables' => [ 'categorylinks', 'page' ],
-                       'fields' => [
-                               'namespace' => NS_CATEGORY,
-                               'title' => 'cl_to',
-                               'value' => 'COUNT(*)'
-                       ],
-                       'conds' => [ 'page_title IS NULL' ],
-                       'options' => [ 'GROUP BY' => 'cl_to' ],
-                       'join_conds' => [ 'page' => [ 'LEFT JOIN',
-                               [ 'page_title = cl_to',
-                                       'page_namespace' => NS_CATEGORY ] ] ]
-               ];
-       }
-
-       function preprocessResults( $db, $res ) {
-               parent::preprocessResults( $db, $res );
-
-               $this->currentCategoryCounts = [];
-
-               if ( !$res->numRows() || !$this->isCached() ) {
-                       return;
-               }
-
-               // Fetch (hopefully) up-to-date numbers of pages in each category.
-               // This should be fast enough as we limit the list to a reasonable length.
-
-               $allCategories = [];
-               foreach ( $res as $row ) {
-                       $allCategories[] = $row->title;
-               }
-
-               $categoryRes = $db->select(
-                       'category',
-                       [ 'cat_title', 'cat_pages' ],
-                       [ 'cat_title' => $allCategories ],
-                       __METHOD__
-               );
-               foreach ( $categoryRes as $row ) {
-                       $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
-               }
-
-               // Back to start for display
-               $res->seek( 0 );
-       }
-
-       /**
-        * @param Skin $skin
-        * @param object $result Result row
-        * @return string
-        */
-       function formatResult( $skin, $result ) {
-               $nt = Title::makeTitle( $result->namespace, $result->title );
-               $text = new HtmlArmor( MediaWikiServices::getInstance()->getContentLanguage()
-                       ->convert( htmlspecialchars( $nt->getText() ) ) );
-
-               if ( !$this->isCached() ) {
-                       // We can assume the freshest data
-                       $plink = $this->getLinkRenderer()->makeBrokenLink(
-                               $nt,
-                               $text
-                       );
-                       $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
-               } else {
-                       $plink = $this->getLinkRenderer()->makeLink( $nt, $text );
-
-                       $currentValue = $this->currentCategoryCounts[$result->title] ?? 0;
-                       $cachedValue = intval( $result->value ); // T76910
-
-                       // If the category has been created or emptied since the list was refreshed, strike it
-                       if ( $nt->isKnown() || $currentValue === 0 ) {
-                               $plink = "<del>$plink</del>";
-                       }
-
-                       // Show the current number of category entries if it changed
-                       if ( $currentValue !== $cachedValue ) {
-                               $nlinks = $this->msg( 'nmemberschanged' )
-                                       ->numParams( $cachedValue, $currentValue )->escaped();
-                       } else {
-                               $nlinks = $this->msg( 'nmembers' )->numParams( $cachedValue )->escaped();
-                       }
-               }
-
-               return $this->getLanguage()->specialList( $plink, $nlinks );
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
deleted file mode 100644 (file)
index 66e6814..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-/**
- * Implements Special:Wantedtemplates
- *
- * Copyright © 2008, Danny B.
- * Based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com>
- *
- * 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 SpecialPage
- * @author Danny B.
- */
-
-/**
- * A querypage to list the most wanted templates
- *
- * @ingroup SpecialPage
- */
-class WantedTemplatesPage extends WantedQueryPage {
-       function __construct( $name = 'Wantedtemplates' ) {
-               parent::__construct( $name );
-       }
-
-       function getQueryInfo() {
-               return [
-                       'tables' => [ 'templatelinks', 'page' ],
-                       'fields' => [
-                               'namespace' => 'tl_namespace',
-                               'title' => 'tl_title',
-                               'value' => 'COUNT(*)'
-                       ],
-                       'conds' => [
-                               'page_title IS NULL',
-                               'tl_namespace' => NS_TEMPLATE
-                       ],
-                       'options' => [ 'GROUP BY' => [ 'tl_namespace', 'tl_title' ] ],
-                       'join_conds' => [ 'page' => [ 'LEFT JOIN',
-                               [ 'page_namespace = tl_namespace',
-                                       'page_title = tl_title' ] ] ]
-               ];
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
diff --git a/includes/specials/SpecialWithoutInterwiki.php b/includes/specials/SpecialWithoutInterwiki.php
new file mode 100644 (file)
index 0000000..36aea75
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Implements Special:Withoutinterwiki
+ *
+ * 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 SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Special page lists pages without language links
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWithoutInterwiki extends PageQueryPage {
+       private $prefix = '';
+
+       function __construct( $name = 'Withoutinterwiki' ) {
+               parent::__construct( $name );
+       }
+
+       function execute( $par ) {
+               $this->prefix = Title::capitalize(
+                       $this->getRequest()->getVal( 'prefix', $par ), NS_MAIN );
+               parent::execute( $par );
+       }
+
+       function getPageHeader() {
+               # Do not show useless input form if special page is cached
+               if ( $this->isCached() ) {
+                       return '';
+               }
+
+               $formDescriptor = [
+                       'prefix' => [
+                               'label-message' => 'allpagesprefix',
+                               'name' => 'prefix',
+                               'id' => 'wiprefix',
+                               'type' => 'text',
+                               'size' => 20,
+                               'default' => $this->prefix
+                       ]
+               ];
+
+               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
+               $htmlForm->setWrapperLegend( '' )
+                       ->setSubmitTextMsg( 'withoutinterwiki-submit' )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function getOrderFields() {
+               return [ 'page_namespace', 'page_title' ];
+       }
+
+       function isExpensive() {
+               return true;
+       }
+
+       function isSyndicated() {
+               return false;
+       }
+
+       function getQueryInfo() {
+               $query = [
+                       'tables' => [ 'page', 'langlinks' ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title'
+                       ],
+                       'conds' => [
+                               'll_title IS NULL',
+                               'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
+                                       getContentNamespaces(),
+                               'page_is_redirect' => 0
+                       ],
+                       'join_conds' => [ 'langlinks' => [ 'LEFT JOIN', 'll_from = page_id' ] ]
+               ];
+               if ( $this->prefix ) {
+                       $dbr = wfGetDB( DB_REPLICA );
+                       $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
+               }
+
+               return $query;
+       }
+
+       protected function getGroupName() {
+               return 'maintenance';
+       }
+}
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
deleted file mode 100644 (file)
index 548e921..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-/**
- * Implements Special:Withoutinterwiki
- *
- * 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 SpecialPage
- * @author Rob Church <robchur@gmail.com>
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Special page lists pages without language links
- *
- * @ingroup SpecialPage
- */
-class WithoutInterwikiPage extends PageQueryPage {
-       private $prefix = '';
-
-       function __construct( $name = 'Withoutinterwiki' ) {
-               parent::__construct( $name );
-       }
-
-       function execute( $par ) {
-               $this->prefix = Title::capitalize(
-                       $this->getRequest()->getVal( 'prefix', $par ), NS_MAIN );
-               parent::execute( $par );
-       }
-
-       function getPageHeader() {
-               # Do not show useless input form if special page is cached
-               if ( $this->isCached() ) {
-                       return '';
-               }
-
-               $formDescriptor = [
-                       'prefix' => [
-                               'label-message' => 'allpagesprefix',
-                               'name' => 'prefix',
-                               'id' => 'wiprefix',
-                               'type' => 'text',
-                               'size' => 20,
-                               'default' => $this->prefix
-                       ]
-               ];
-
-               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
-               $htmlForm->setWrapperLegend( '' )
-                       ->setSubmitTextMsg( 'withoutinterwiki-submit' )
-                       ->setMethod( 'get' )
-                       ->prepareForm()
-                       ->displayForm( false );
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function getOrderFields() {
-               return [ 'page_namespace', 'page_title' ];
-       }
-
-       function isExpensive() {
-               return true;
-       }
-
-       function isSyndicated() {
-               return false;
-       }
-
-       function getQueryInfo() {
-               $query = [
-                       'tables' => [ 'page', 'langlinks' ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title'
-                       ],
-                       'conds' => [
-                               'll_title IS NULL',
-                               'page_namespace' => MediaWikiServices::getInstance()->getNamespaceInfo()->
-                                       getContentNamespaces(),
-                               'page_is_redirect' => 0
-                       ],
-                       'join_conds' => [ 'langlinks' => [ 'LEFT JOIN', 'll_from = page_id' ] ]
-               ];
-               if ( $this->prefix ) {
-                       $dbr = wfGetDB( DB_REPLICA );
-                       $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
-               }
-
-               return $query;
-       }
-
-       protected function getGroupName() {
-               return 'maintenance';
-       }
-}
index 7703e20..0abe842 100644 (file)
@@ -43,6 +43,12 @@ class ActiveUsersPager extends UsersPager {
         */
        private $blockStatusByUid;
 
+       /** @var int */
+       private $RCMaxAge;
+
+       /** @var string[] */
+       private $excludegroups;
+
        /**
         * @param IContextSource|null $context
         * @param FormOptions $opts
@@ -79,19 +85,16 @@ class ActiveUsersPager extends UsersPager {
        function getQueryInfo( $data = null ) {
                $dbr = $this->getDatabase();
 
-               $useActor = (bool)(
-                       $this->getConfig()->get( 'ActorTableSchemaMigrationStage' ) & SCHEMA_COMPAT_READ_NEW
-               );
-
                $activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
                $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
                $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
 
                // Inner subselect to pull the active users out of querycachetwo
-               $tables = [ 'querycachetwo', 'user' ];
-               $fields = [ 'qcc_title', 'user_id' ];
+               $tables = [ 'querycachetwo', 'user', 'actor' ];
+               $fields = [ 'qcc_title', 'user_id', 'actor_id' ];
                $jconds = [
                        'user' => [ 'JOIN', 'user_name = qcc_title' ],
+                       'actor' => [ 'JOIN', 'actor_user = user_id' ],
                ];
                $conds = [
                        'qcc_type' => 'activeusers',
@@ -126,20 +129,12 @@ class ActiveUsersPager extends UsersPager {
                                        'ipblocks', '1', [ 'ipb_user=user_id', 'ipb_deleted' => 1 ]
                                ) . ')';
                }
-               if ( $useActor ) {
-                       $tables[] = 'actor';
-                       $jconds['actor'] = [
-                               'JOIN',
-                               'actor_user = user_id',
-                       ];
-                       $fields[] = 'actor_id';
-               }
                $subquery = $dbr->buildSelectSubquery( $tables, $fields, $conds, $fname, $options, $jconds );
 
                // Outer query to select the recent edit counts for the selected active users
                $tables = [ 'qcc_users' => $subquery, 'recentchanges' ];
                $jconds = [ 'recentchanges' => [ 'LEFT JOIN', [
-                       $useActor ? 'rc_actor = actor_id' : 'rc_user_text = qcc_title',
+                       'rc_actor = actor_id',
                        'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
                        'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE ), // Don't count categorization changes.
                        'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
index c804b09..6b8b93b 100644 (file)
@@ -46,6 +46,11 @@ class AllMessagesTablePager extends TablePager {
         */
        protected $prefix;
 
+       /**
+        * @var string
+        */
+       protected $suffix;
+
        /**
         * @var Language
         */
index 9415cea..81c3ffb 100644 (file)
@@ -30,10 +30,15 @@ class MergeHistoryPager extends ReverseChronologicalPager {
        /** @var array */
        public $mConds;
 
+       /** @var int */
+       private $articleID;
+
+       /** @var int */
+       private $maxTimestamp;
+
        public function __construct( SpecialMergeHistory $form, $conds, Title $source, Title $dest ) {
                $this->mForm = $form;
                $this->mConds = $conds;
-               $this->title = $source;
                $this->articleID = $source->getArticleID();
 
                $dbr = wfGetDB( DB_REPLICA );
index f1b0b9a..be4a1bd 100644 (file)
@@ -97,27 +97,17 @@ class NewFilesPager extends RangeChronologicalPager {
                }
 
                if ( $opts->getValue( 'hidepatrolled' ) ) {
-                       global $wgActorTableSchemaMigrationStage;
-
                        $tables[] = 'recentchanges';
                        $conds['rc_type'] = RC_LOG;
                        $conds['rc_log_type'] = 'upload';
                        $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
                        $conds['rc_namespace'] = NS_FILE;
 
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                               $jcond = 'rc_actor = ' . $imgQuery['fields']['img_actor'];
-                       } else {
-                               $rcQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
-                               $tables += $rcQuery['tables'];
-                               $jconds += $rcQuery['joins'];
-                               $jcond = $rcQuery['fields']['rc_user'] . ' = ' . $imgQuery['fields']['img_user'];
-                       }
                        $jconds['recentchanges'] = [
                                'JOIN',
                                [
                                        'rc_title = img_name',
-                                       $jcond,
+                                       'rc_actor = ' . $imgQuery['fields']['img_actor'],
                                        'rc_timestamp = img_timestamp'
                                ]
                        ];
index 296fe11..a00b371 100644 (file)
@@ -34,6 +34,12 @@ class ProtectedTitlesPager extends AlphabeticPager {
         */
        public $mConds;
 
+       /** @var string|null */
+       private $level;
+
+       /** @var int|null */
+       private $namespace;
+
        /**
         * @param SpecialProtectedtitles $form
         * @param array $conds
@@ -50,7 +56,6 @@ class ProtectedTitlesPager extends AlphabeticPager {
                $this->mConds = $conds;
                $this->level = $level;
                $this->namespace = $namespace;
-               $this->size = intval( $size );
                parent::__construct( $form->getContext() );
        }
 
@@ -90,7 +95,7 @@ class ProtectedTitlesPager extends AlphabeticPager {
                        $conds['pt_create_perm'] = $this->level;
                }
 
-               if ( !is_null( $this->namespace ) ) {
+               if ( $this->namespace !== null ) {
                        $conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace );
                }
 
index 57b575b..ba078e9 100644 (file)
@@ -37,6 +37,24 @@ class UsersPager extends AlphabeticPager {
         */
        protected $userGroupCache;
 
+       /** @var string */
+       protected $requestedGroup;
+
+       /** @var bool */
+       protected $editsOnly;
+
+       /** @var bool */
+       protected $temporaryGroupsOnly;
+
+       /** @var bool */
+       protected $creationSort;
+
+       /** @var bool|null */
+       protected $including;
+
+       /** @var string */
+       protected $requestedUser;
+
        /**
         * @param IContextSource|null $context
         * @param array|null $par (Default null)
index a9f399b..7f4d7c5 100644 (file)
@@ -40,14 +40,11 @@ class UploadFromStash extends UploadBase {
        private $repo;
 
        /**
-        * @param User|bool $user Default: false
+        * @param User|bool $user Default: false Sometimes this won't exist, as when running from cron.
         * @param UploadStash|bool $stash Default: false
         * @param FileRepo|bool $repo Default: false
         */
        public function __construct( $user = false, $stash = false, $repo = false ) {
-               // user object. sometimes this won't exist, as when running from cron.
-               $this->user = $user;
-
                if ( $repo ) {
                        $this->repo = $repo;
                } else {
@@ -63,7 +60,7 @@ class UploadFromStash extends UploadBase {
                                wfDebug( __METHOD__ . " creating new UploadStash instance with no user\n" );
                        }
 
-                       $this->stash = new UploadStash( $this->repo, $this->user );
+                       $this->stash = new UploadStash( $this->repo, $user );
                }
        }
 
index 7068879..4445e1d 100644 (file)
@@ -64,7 +64,7 @@ class User implements IDBAccessObject, UserIdentity {
         * Version number to tag cached versions of serialized User objects. Should be increased when
         * {@link $mCacheVars} or one of it's members changes.
         */
-       const VERSION = 13;
+       const VERSION = 14;
 
        /**
         * Exclude user options that are set to their default value.
@@ -327,22 +327,6 @@ class User implements IDBAccessObject, UserIdentity {
                        case 'defaults':
                                $this->loadDefaults();
                                break;
-                       case 'name':
-                               // Make sure this thread sees its own changes
-                               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-                               if ( $lb->hasOrMadeRecentMasterChanges() ) {
-                                       $flags |= self::READ_LATEST;
-                                       $this->queryFlagsUsed = $flags;
-                               }
-
-                               $this->mId = self::idFromName( $this->mName, $flags );
-                               if ( !$this->mId ) {
-                                       // Nonexistent user placeholder object
-                                       $this->loadDefaults( $this->mName );
-                               } else {
-                                       $this->loadFromId( $flags );
-                               }
-                               break;
                        case 'id':
                                // Make sure this thread sees its own changes, if the ID isn't 0
                                if ( $this->mId != 0 ) {
@@ -356,6 +340,7 @@ class User implements IDBAccessObject, UserIdentity {
                                $this->loadFromId( $flags );
                                break;
                        case 'actor':
+                       case 'name':
                                // Make sure this thread sees its own changes
                                $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                                if ( $lb->hasOrMadeRecentMasterChanges() ) {
@@ -366,20 +351,20 @@ class User implements IDBAccessObject, UserIdentity {
                                list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
                                $row = wfGetDB( $index )->selectRow(
                                        'actor',
-                                       [ 'actor_user', 'actor_name' ],
-                                       [ 'actor_id' => $this->mActorId ],
+                                       [ 'actor_id', 'actor_user', 'actor_name' ],
+                                       $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
                                        __METHOD__,
                                        $options
                                );
 
                                if ( !$row ) {
                                        // Ugh.
-                                       $this->loadDefaults();
+                                       $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
                                } elseif ( $row->actor_user ) {
                                        $this->mId = $row->actor_user;
                                        $this->loadFromId( $flags );
                                } else {
-                                       $this->loadDefaults( $row->actor_name );
+                                       $this->loadDefaults( $row->actor_name, $row->actor_id );
                                }
                                break;
                        case 'session':
@@ -567,17 +552,6 @@ class User implements IDBAccessObject, UserIdentity {
         * @return User The corresponding User object
         */
        public static function newFromActorId( $id ) {
-               global $wgActorTableSchemaMigrationStage;
-
-               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
-               // but it does little harm and might be needed for write callers loading a User.
-               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
-                       );
-               }
-
                $u = new User;
                $u->mActorId = $id;
                $u->mFrom = 'actor';
@@ -620,8 +594,6 @@ class User implements IDBAccessObject, UserIdentity {
         * @return User
         */
        public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
-               global $wgActorTableSchemaMigrationStage;
-
                // Stop-gap solution for the problem described in T222212.
                // Force the User ID and Actor ID to zero for users loaded from the database
                // of another wiki, to prevent subtle data corruption and confusing failure modes.
@@ -633,9 +605,7 @@ class User implements IDBAccessObject, UserIdentity {
                $user = new User;
                $user->mFrom = 'defaults';
 
-               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
-               // but it does little harm and might be needed for write callers loading a User.
-               if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
+               if ( $actorId !== null ) {
                        $user->mActorId = (int)$actorId;
                        if ( $user->mActorId !== 0 ) {
                                $user->mFrom = 'actor';
@@ -1220,11 +1190,12 @@ class User implements IDBAccessObject, UserIdentity {
         *       the constructor does that instead.
         *
         * @param string|bool $name
+        * @param int|null $actorId
         */
-       public function loadDefaults( $name = false ) {
+       public function loadDefaults( $name = false, $actorId = null ) {
                $this->mId = 0;
                $this->mName = $name;
-               $this->mActorId = null;
+               $this->mActorId = $actorId;
                $this->mRealName = '';
                $this->mEmail = '';
                $this->mOptionOverrides = null;
@@ -1375,8 +1346,6 @@ class User implements IDBAccessObject, UserIdentity {
         *  user_properties   Array with properties out of the user_properties table
         */
        protected function loadFromRow( $row, $data = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
                if ( !is_object( $row ) ) {
                        throw new InvalidArgumentException( '$row must be an object' );
                }
@@ -1385,18 +1354,14 @@ class User implements IDBAccessObject, UserIdentity {
 
                $this->mGroupMemberships = null; // deferred
 
-               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
-               // but it does little harm and might be needed for write callers loading a User.
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
-                       if ( isset( $row->actor_id ) ) {
-                               $this->mActorId = (int)$row->actor_id;
-                               if ( $this->mActorId !== 0 ) {
-                                       $this->mFrom = 'actor';
-                               }
-                               $this->setItemLoaded( 'actor' );
-                       } else {
-                               $all = false;
+               if ( isset( $row->actor_id ) ) {
+                       $this->mActorId = (int)$row->actor_id;
+                       if ( $this->mActorId !== 0 ) {
+                               $this->mFrom = 'actor';
                        }
+                       $this->setItemLoaded( 'actor' );
+               } else {
+                       $all = false;
                }
 
                if ( isset( $row->user_name ) && $row->user_name !== '' ) {
@@ -1776,7 +1741,7 @@ class User implements IDBAccessObject, UserIdentity {
                // Avoid PHP 7.1 warning of passing $this by reference
                $thisUser = $this;
                // Extensions
-               Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
+               Hooks::run( 'GetBlockedStatus', [ &$thisUser ], '1.34' );
        }
 
        /**
@@ -2204,7 +2169,7 @@ class User implements IDBAccessObject, UserIdentity {
                if ( !$this->mHideName ) {
                        // Reset for hook
                        $this->mHideName = false;
-                       Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
+                       Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ], '1.34' );
                }
                return (bool)$this->mHideName;
        }
@@ -2214,7 +2179,9 @@ class User implements IDBAccessObject, UserIdentity {
         * @return int The user's ID; 0 if the user is anonymous or nonexistent
         */
        public function getId() {
-               if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
+               if ( $this->mId === null && $this->mName !== null &&
+                       ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
+               ) {
                        // Special case, we know the user is anonymous
                        return 0;
                }
@@ -2280,62 +2247,43 @@ class User implements IDBAccessObject, UserIdentity {
         * @return int The actor's ID, or 0 if no actor ID exists and $dbw was null
         */
        public function getActorId( IDatabase $dbw = null ) {
-               global $wgActorTableSchemaMigrationStage;
-
-               // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
-               // but it does little harm and might be needed for write callers loading a User.
-               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
-                       return 0;
-               }
-
                if ( !$this->isItemLoaded( 'actor' ) ) {
                        $this->load();
                }
 
-               // Currently $this->mActorId might be null if $this was loaded from a
-               // cache entry that was written when $wgActorTableSchemaMigrationStage
-               // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
-               // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
-               // has been removed), that condition may be removed.
-               if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
+               if ( !$this->mActorId && $dbw ) {
                        $q = [
                                'actor_user' => $this->getId() ?: null,
                                'actor_name' => (string)$this->getName(),
                        ];
-                       if ( $dbw ) {
-                               if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
+                       if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
+                               throw new CannotCreateActorException(
+                                       'Cannot create an actor for a usable name that is not an existing user'
+                               );
+                       }
+                       if ( $q['actor_name'] === '' ) {
+                               throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
+                       }
+                       $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
+                       if ( $dbw->affectedRows() ) {
+                               $this->mActorId = (int)$dbw->insertId();
+                       } else {
+                               // Outdated cache?
+                               // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
+                               $this->mActorId = (int)$dbw->selectField(
+                                       'actor',
+                                       'actor_id',
+                                       $q,
+                                       __METHOD__,
+                                       [ 'LOCK IN SHARE MODE' ]
+                               );
+                               if ( !$this->mActorId ) {
                                        throw new CannotCreateActorException(
-                                               'Cannot create an actor for a usable name that is not an existing user'
-                                       );
-                               }
-                               if ( $q['actor_name'] === '' ) {
-                                       throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
-                               }
-                               $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
-                               if ( $dbw->affectedRows() ) {
-                                       $this->mActorId = (int)$dbw->insertId();
-                               } else {
-                                       // Outdated cache?
-                                       // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
-                                       $this->mActorId = (int)$dbw->selectField(
-                                               'actor',
-                                               'actor_id',
-                                               $q,
-                                               __METHOD__,
-                                               [ 'LOCK IN SHARE MODE' ]
+                                               "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
                                        );
-                                       if ( !$this->mActorId ) {
-                                               throw new CannotCreateActorException(
-                                                       "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
-                                               );
-                                       }
                                }
-                               $this->invalidateCache();
-                       } else {
-                               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
-                               $db = wfGetDB( $index );
-                               $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
                        }
+                       $this->invalidateCache();
                        $this->setItemLoaded( 'actor' );
                }
 
@@ -3769,7 +3717,7 @@ class User implements IDBAccessObject, UserIdentity {
 
                                // If there is a new, unseen, revision, use its timestamp
                                $nextid = $oldid
-                                       ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
+                                       ? $title->getNextRevisionID( $oldid, Title::READ_LATEST )
                                        : null;
                                if ( $nextid ) {
                                        $this->setNewtalk( true, Revision::newFromId( $nextid ) );
@@ -3979,8 +3927,6 @@ class User implements IDBAccessObject, UserIdentity {
 
                $dbw = wfGetDB( DB_MASTER );
                $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
-                       global $wgActorTableSchemaMigrationStage;
-
                        $dbw->update( 'user',
                                [ /* SET */
                                        'user_name' => $this->mName,
@@ -4010,14 +3956,12 @@ class User implements IDBAccessObject, UserIdentity {
                                );
                        }
 
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $dbw->update(
-                                       'actor',
-                                       [ 'actor_name' => $this->mName ],
-                                       [ 'actor_user' => $this->mId ],
-                                       $fname
-                               );
-                       }
+                       $dbw->update(
+                               'actor',
+                               [ 'actor_name' => $this->mName ],
+                               [ 'actor_user' => $this->mId ],
+                               $fname
+                       );
                } );
 
                $this->mTouched = $newTouched;
@@ -4216,16 +4160,12 @@ class User implements IDBAccessObject, UserIdentity {
         * @param IDatabase $dbw Writable database handle
         */
        private function updateActorId( IDatabase $dbw ) {
-               global $wgActorTableSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $dbw->insert(
-                               'actor',
-                               [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
-                               __METHOD__
-                       );
-                       $this->mActorId = (int)$dbw->insertId();
-               }
+               $dbw->insert(
+                       'actor',
+                       [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
+                       __METHOD__
+               );
+               $this->mActorId = (int)$dbw->insertId();
        }
 
        /**
@@ -5296,10 +5236,8 @@ class User implements IDBAccessObject, UserIdentity {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getQueryInfo() {
-               global $wgActorTableSchemaMigrationStage;
-
                $ret = [
-                       'tables' => [ 'user' ],
+                       'tables' => [ 'user', 'user_actor' => 'actor' ],
                        'fields' => [
                                'user_id',
                                'user_name',
@@ -5312,21 +5250,13 @@ class User implements IDBAccessObject, UserIdentity {
                                'user_email_token_expires',
                                'user_registration',
                                'user_editcount',
+                               'user_actor.actor_id',
+                       ],
+                       'joins' => [
+                               'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
                        ],
-                       'joins' => [],
                ];
 
-               // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
-               // but it does little harm and might be needed for write callers loading a User.
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
-                       $ret['tables']['user_actor'] = 'actor';
-                       $ret['fields'][] = 'user_actor.actor_id';
-                       $ret['joins']['user_actor'] = [
-                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
-                               [ 'user_actor.actor_user = user_id' ]
-                       ];
-               }
-
                return $ret;
        }
 
index 72f6086..69dcec8 100644 (file)
@@ -57,7 +57,10 @@ class NoWriteWatchedItemStore implements WatchedItemStoreInterface {
        }
 
        public function countWatchersMultiple( array $targets, array $options = [] ) {
-               return $this->actualStore->countVisitingWatchersMultiple( $targets, $options );
+               return $this->actualStore->countVisitingWatchersMultiple(
+                       $targets,
+                       $options['minimumWatchers'] ?? null
+               );
        }
 
        public function countVisitingWatchersMultiple(
index 06d8095..3ae00ea 100644 (file)
@@ -9,14 +9,22 @@ namespace MediaWiki\Widget;
  * @license MIT
  */
 class CheckMatrixWidget extends \OOUI\Widget {
-
-       protected $name = '';
-       protected $columns = [];
-       protected $rows = [];
-       protected $tooltips = [];
-       protected $values = [];
-       protected $forcedOn = [];
-       protected $forcedOff = [];
+       /** @var string|null */
+       protected $name;
+       /** @var string|null */
+       protected $id;
+       /** @var array */
+       protected $columns;
+       /** @var array */
+       protected $rows;
+       /** @var array */
+       protected $tooltips;
+       /** @var array */
+       protected $values;
+       /** @var array */
+       protected $forcedOn;
+       /** @var array */
+       protected $forcedOff;
 
        /**
         * Operates similarly to MultiSelectWidget, but instead of using an array of
index 7737067..913816c 100644 (file)
@@ -9,7 +9,8 @@ namespace MediaWiki\Widget;
  * @license MIT
  */
 class ComplexTitleInputWidget extends \OOUI\Widget {
-
+       /** @var array */
+       protected $config;
        protected $namespace = null;
        protected $title = null;
 
index 7802a2a..a360fb8 100644 (file)
@@ -9,8 +9,10 @@ namespace MediaWiki\Widget;
  * @license MIT
  */
 class NamespaceInputWidget extends \OOUI\DropdownInputWidget {
-
-       protected $includeAllValue = null;
+       /** @var string */
+       protected $includeAllValue;
+       /** @var int[] */
+       protected $exclude;
 
        /**
         * @param array $config Configuration options
index a946653..a792172 100644 (file)
@@ -12,9 +12,12 @@ use OOUI\TextInputWidget;
  * @license MIT
  */
 class SelectWithInputWidget extends \OOUI\Widget {
-
-       protected $textinput = null;
-       protected $dropdowninput = null;
+       /** @var array */
+       protected $config;
+       /** @var TextInputWidget */
+       protected $textinput;
+       /** @var DropdownInputWidget */
+       protected $dropdowninput;
 
        /**
         * A version of the SelectWithInputWidget, with `or` set to true.
index 18c05bf..26935b1 100644 (file)
@@ -13,9 +13,14 @@ use \OOUI\LabelWidget;
  * @license MIT
  */
 class SizeFilterWidget extends \OOUI\Widget {
-
-       protected $radioselectinput = null;
-       protected $textinput = null;
+       /** @var array */
+       protected $config;
+       /** @var LabelWidget */
+       protected $label;
+       /** @var RadioSelectInputWidget */
+       protected $radioselectinput;
+       /** @var TextInputWidget */
+       protected $textinput;
 
        /**
         * RadioSelectInputWidget and a TextInputWidget to set minimum or maximum byte size
index 43e184c..e96160c 100644 (file)
@@ -12,41 +12,34 @@ use OOUI\MultilineTextInputWidget;
  * @license MIT
  */
 abstract class TagMultiselectWidget extends \OOUI\Widget {
-
-       protected $selectedArray = [];
-       protected $inputName = null;
-       protected $inputPlaceholder = null;
-       protected $tagLimit = null;
+       /** @var array */
+       protected $selectedArray;
+       /** @var string|null */
+       protected $inputName;
+       /** @var string|null */
+       protected $inputPlaceholder;
+       /** @var array */
+       protected $input;
+       /** @var int|null */
+       protected $tagLimit;
 
        /**
         * @param array $config Configuration options
         *   - array $config['default'] Array of items to use as preset data
-        *   - array $config['name'] Name attribute (used in forms)
-        *   - array $config['placeholder'] Placeholder message for input
+        *   - string $config['name'] Name attribute (used in forms)
+        *   - string $config['placeholder'] Placeholder message for input
         *   - array $config['input'] Config options for the input widget
-        *   - number $config['tagLimit'] Maximum number of selected items
+        *   - int $config['tagLimit'] Maximum number of selected items
         */
        public function __construct( array $config = [] ) {
                parent::__construct( $config );
 
                // Properties
-               if ( isset( $config['default'] ) ) {
-                       $this->selectedArray = $config['default'];
-               }
-               if ( isset( $config['name'] ) ) {
-                       $this->inputName = $config['name'];
-               }
-               if ( isset( $config['placeholder'] ) ) {
-                       $this->inputPlaceholder = $config['placeholder'];
-               }
-               if ( isset( $config['input'] ) ) {
-                       $this->input = $config['input'];
-               } else {
-                       $this->input = [];
-               }
-               if ( isset( $config['tagLimit'] ) ) {
-                       $this->tagLimit = $config['tagLimit'];
-               }
+               $this->selectedArray = $config['default'] ?? [];
+               $this->inputName = $config['name'] ?? null;
+               $this->inputPlaceholder = $config['placeholder'] ?? null;
+               $this->input = $config['input'] ?? [];
+               $this->tagLimit = $config['tagLimit'] ?? null;
 
                $textarea = new MultilineTextInputWidget( array_merge( [
                        'name' => $this->inputName,
index 0df4cd0..5c2d290 100644 (file)
--- a/index.php
+++ b/index.php
@@ -30,6 +30,8 @@
  * @file
  */
 
+define( 'MW_ENTRY_POINT', 'index' );
+
 // Bail on old versions of PHP, or if composer has not been run yet to install
 // dependencies. Using dirname( __FILE__ ) here because __DIR__ is PHP5.3+.
 // phpcs:ignore MediaWiki.Usage.DirUsage.FunctionFound
index 3508483..fc8e5f7 100644 (file)
        "undo-norev": "فشل في الرجوع عن التعديل حيث أنه غير موجود أو تم حذفه.",
        "undo-nochange": "يبدو أن التعديل قد تم التراجع عنه بالفعل.",
        "undo-summary": "الرجوع عن التعديل $1 بواسطة [[Special:Contributions/$2|$2]] ([[User talk:$2|نقاش]])",
+       "undo-summary-anon": "التراجع عن المراجعة $1 بواسطة [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "الرجوع عن المراجعة $1 التي أجراها مستخدمي مخفي",
        "cantcreateaccount-text": "إنشاء الحسابات من عنوان الأيبي هذا (<strong>$1</strong>) تم منعه بواسطة [[User:$3|$3]].\n\nالسبب المعطى بواسطة $3 هو <em>$2</em>",
        "cantcreateaccount-range-text": "إنشاء الحسابات من عناوين الآيبي في النطاق <strong>$1</strong>، التي تحتوي على الآيبي الخاص بك (<strong>$4</strong>)، قد منعها [[User:$3|$3]].\n\nالسبب المعطى بواسطة $3 هو <em>$2</em>",
        "alreadyrolled": "لم يمكن استرجاع آخر تعديل ل[[$1]] بواسطة [[User:$2|$2]] ([[User talk:$2|نقاش]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])؛\nشخص آخر عدل أو استرجع الصفحة بالفعل.\n\nآخر تعديل كان بواسطة [[User:$3|$3]] ([[User talk:$3|نقاش]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "ملخص التعديل كان:<em>$1</em>.",
        "revertpage": "استرجع تعديلات [[Special:Contributions/$2|$2]] ([[User talk:$2|نقاش]]) حتى آخر مراجعة ل[[User:$1|$1]]",
+       "revertpage-anon": "استرجع تعديلات [[Special:Contributions/$2|$2]] حتى آخر مراجعة بواسطة [[User:$1|$1]]",
        "revertpage-nouser": "استرجع تعديلات مستخدم مخفي حتى آخر مراجعة ل{{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "تم استرجاع تعديلات {{GENDER:$3|$1}}، حتى آخر نسخة بواسطة {{GENDER:$4|$2}}.",
        "sessionfailure-title": "فشل في الجلسة",
index 469cb7e..c5a76e6 100644 (file)
        "editlink": "editar",
        "viewsourcelink": "ver fonte",
        "editsectionhint": "Editar seición: $1",
-       "toc": "Índiz",
+       "toc": "Conteníu",
        "showtoc": "amosar",
        "hidetoc": "anubrir",
        "collapsible-collapse": "Plegar",
index 810c304..402019e 100644 (file)
        "privacypage": "Project:Gizlilik prinsipi",
        "badaccess": "İcazə xətası",
        "badaccess-group0": "Bu fəaliyyəti icra etmək səlahiyyətiniz yoxdur.",
-       "badaccess-groups": "Bu fəaliyyəti, yalnız $1 {{PLURAL:$2|qrupundakı|qruplarından birindəki}} istifadəçilər icra edə bilərlər.",
+       "badaccess-groups": "Bu fəaliyyəti yalnız \"$1\" {{PLURAL:$2|qrupundakı|qruplarından birindəki}} istifadəçilər icra edə bilərlər.",
        "versionrequired": "MediaViki $1 versiyası lazımdır",
        "versionrequiredtext": "Bu səhifəni istifadə etmək üçün MediaVikinin $1 versiyası tələb olunur.\nBax: [[Special:Version|Versiyalar]].",
        "ok": "OK",
index 1b3950f..37e5947 100644 (file)
        "youhavenewmessagesfromusers": "{{PLURAL:$4|Ida dané madué}} $1 saking {{PLURAL:$3|$3 sang anganggé lianan}} ($2).",
        "youhavenewmessagesmanyusers": "Jero madué $1 saking akéh sang anganggé ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|séwalapatra anyar abesik|999=séwalapatra anyar}}",
+       "newmessagesdifflinkplural": "$1 {{PLURAL:$1|uahan}}",
        "youhavenewmessagesmulti": "Ida dané madué séwalapatra anyar ring $1",
        "editsection": "uah",
        "editold": "uah",
        "template-protected": "(kasaibin)",
        "template-semiprotected": "(semi-kasaibin)",
        "hiddencategories": "lembar niki inggih punika krama saking {{PLURAL:$1|1 golongan sane mengkeb|$1 golongan sane mengkeb}}",
+       "permissionserrors": "Kaiwangan ritatkala ngranjing",
        "permissionserrorstext-withaction": "ida dané nénten madué kuasa ngranjing anggén $2, riantukan {{PLURAL:$1|alasan}} ring sor puniki:",
        "recreate-moveddeleted-warn": "\"pingetan\" ida dane ngawe malih lembar sane naenin maapus.'''\n\nmangda kayunin malih napike pantes lanturang suntingan ida dane. puniki log pengapusan lan pangisidan saking lembar puniki:",
        "moveddeleted-notice": "Kaca puniki sampun kausapin.\nAnggen pewarah, proteksi, lan pengisidan log saking lembar puniki cingakin pustaka beten.",
        "editundo": "nguliang",
        "diff-empty": "(Nénten wénten sané malianan)",
        "diff-multi-sameuser": "({{PLURAL:$1|$1 revisi pantaraning}} olih pangawi sane pateh nenten kacumawisang)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Siki pamecikan pantaraning|$1 pamecikan pantaraning}} olih {{PLURAL:$2|siki pangangge lianan|$2 panggange}} nenten katampilan)",
        "searchresults": "asil pangrereh",
        "searchresults-title": "Asil pangrereh anggén \"$1\"",
        "prevn": "{{PLURAL:$1|$1}} sadurungnyané",
        "group-bot": "Bot",
        "group-sysop": "Prajuru",
        "grouppage-bot": "{{ns:project}}:Bot",
+       "grouppage-sysop": "{{ns:project}}:Prajuru",
        "right-edit": "Uah kaca",
        "right-writeapi": "nganggén API sasuratan",
        "right-delete": "Usap kaca",
        "randompage": "Kaca ulah-aluh",
        "statistics": "Statistik",
        "statistics-articles": "Kaca daging",
+       "double-redirect-fixer": "Pamecikan pengalihan",
        "brokenredirects-edit": "uah",
        "brokenredirects-delete": "usap",
        "withoutinterwiki-submit": "Sinahang",
        "booksources": "Wit buku",
        "booksources-search-legend": "Rereh wit buku",
        "booksources-search": "Rereh",
+       "specialloguserlabel": "Panggange",
        "log": "Log",
        "logeventslist-submit": "Sinahang",
        "all-logs-page": "Makasami log publik",
+       "logempty": "Nenten katemonin entri log sane patut",
        "allpages": "Makasami kaca",
        "allarticles": "Makasami kaca",
        "allinnamespace": "Makasami kaca (genah wastan $1)",
        "listgrouprights-members": "kepahan krama",
        "emailuser": "email sane nganggo niki",
        "emailmessage": "Séwalapatra:",
+       "usermessage-editor": "Séwalapatra sistem",
        "watchlist": "kepahan peninjoan",
        "mywatchlist": "kepahan peninjoan",
        "watchlistfor2": "Anggén $1 $2",
        "pageinfo-robot-index": "Kalugra",
        "pageinfo-robot-noindex": "Tan kalugra",
        "pageinfo-watchers": "Akéh nomér sané negdeg kaca",
+       "pageinfo-few-watchers": "Kirang saking $1 {{PLURAL:$1|tamiu}}",
        "pageinfo-redirects-name": "Akéh nomer sané magingsir ka kaca puniki",
        "pageinfo-subpages-name": "Kapahan kaca saking kaca puniki",
        "pageinfo-firstuser": "Sang makarya kaca",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|pabligbagan]])",
        "duplicate-defaultsort": "pingetan: sereg pangurutan lingga \"$2\" nyampahang sereg pangurutan lingga sadurunge \"$1\"",
        "version-specialpages": "Kaca kusus",
+       "redirect": "Pengalihan manut bacakan ID , panggage, kaca, pamecikan, utawi log",
+       "redirect-summary": "Kaca istimewa puniki kaglingsiran (manut wastan bacakannyane),kaca (manut pamecikan), utawi kacan pangangge (manut ID nomer panganggenyane). Kawigunan:\n[[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], atau [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Nglanturang",
        "redirect-lookup": "Rereh:",
        "redirect-value": "Aji:",
        "tags-delete-title": "Usap tag",
        "compare-page2": "Kaca 2",
        "logentry-delete-delete": "$1 {{GENDER:$2|ngusapin}} kaca $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|ngwalikan}} kaca $3 ($4)",
+       "logentry-delete-revision": "$1 {{GENDER:$2|mauah}} kaca utama {{PLURAL:$5|$5  pamecikan}} ring kaca $3: $4",
        "revdelete-content-hid": "dagingnyané kaengkebang",
        "logentry-move-move": "$1 {{GENDER:$2|ngingsirang}} kaca $3 ring $4",
+       "logentry-move-move-noredirect": "$1 {{GENDER:$2|maglisiran}} kaca $3 ka $4 tur nenten ngawe pengalihan",
+       "logentry-move-move_redir": "$1 {{GENDER:$2|maglisiran}} kaca $3 ka $4 tur nenten ngawe pengalihan",
        "logentry-newusers-create": "Akun sang anganggé $1 {{GENDER:$2|kakaryanin}}",
        "logentry-newusers-autocreate": "Akun sang anganggé $1 {{GENDER:$2|kakaryanin}} otomatis",
        "logentry-protect-protect": "$1 {{GENDER:$2|nyaibin}} $3 $4",
        "pagelanguage": "Uah basa ring kaca",
        "pagelang-nonexistent-page": "Kaca $1 nénten wénten.",
        "mw-widgets-abandonedit-keep": "Lanturang nguah",
+       "randomrootpage": "Kaca pinih dasar sane mabrarakan",
        "log-action-filter-protect-protect": "Saiban",
        "log-action-filter-protect-move_prot": "Ngingsirang saiban"
 }
index bc5c403..485c9fe 100644 (file)
        "sessionfailure": "Изглежда има проблем със сесията ви;\nдействието беше отказано като предпазна мярка срещу крадене на сесията.\nМоля, изпратете формуляра повторно.",
        "changecontentmodel": "Промяна на модела на съдържанието на страница",
        "changecontentmodel-legend": "Промяна на модела на съдържанието",
-       "changecontentmodel-title-label": "Заглавие на страницата",
-       "changecontentmodel-model-label": "Нов модел на съдържанието",
+       "changecontentmodel-title-label": "Заглавие на страницата:",
+       "changecontentmodel-model-label": "Нов модел на съдържанието:",
        "changecontentmodel-reason-label": "Причина:",
        "changecontentmodel-submit": "Променяне",
        "changecontentmodel-success-title": "Моделът на съдържанието беше променен",
index a309520..224e4ae 100644 (file)
        "sessionfailure": "আপনার প্রবেশ সেশনে একটি সমস্যা হয়েছে বলে মনে হচ্ছে;\nসেশন হাইজ্যাক প্রতিরোধের উপায় হিসেবে এই কাজটি বাতিল করা হয়েছে।\nদয়া করে ফরমটি পুনরায় জমা দিন।",
        "changecontentmodel": "একটি পাতার বিষয়বস্তুর রূপ পরিবর্তন",
        "changecontentmodel-legend": "বিষয়বস্তুর রূপ পরিবর্তন করুন",
-       "changecontentmodel-title-label": "পাতার শিরোনাম",
+       "changecontentmodel-title-label": "পাতার শিরোনাম:",
        "changecontentmodel-current-label": "বর্তমান বিষয়বস্তুর রূপ:",
-       "changecontentmodel-model-label": "পাতার বিষয়বস্তুর প্রতিরূপ",
+       "changecontentmodel-model-label": "পাতার বিষয়বস্তুর প্রতিরূপ:",
        "changecontentmodel-reason-label": "কারণ:",
        "changecontentmodel-submit": "পরিবর্তন করুন",
        "changecontentmodel-success-title": "বিষয়বস্তুর প্রতিরূপ পরিবর্তিত হয়েছিলো",
        "move-subpages": "উপপাতা স্থানান্তর ($1টি পর্যন্ত)",
        "move-talk-subpages": "উপপাতার আলাপ পাতা স্থানান্তর ($1টি পর্যন্ত)",
        "movepage-page-exists": "$1 পাতাটি ইতিমধ্যেই বিদ্যমান এবং স্বয়ংক্রিয়ভাবে পুনর্লিখন করা সম্ভব নয়।",
+       "movepage-source-doesnt-exist": "$1 পাতাটির অস্তিত্ব নেই এবং স্থানান্তর করা যাবে না।",
        "movepage-page-moved": "$1 পাতাটি $2 পাতায় স্থানান্তর করা হয়েছে।",
        "movepage-page-unmoved": "$1 পাতাটি $2 -এ সরিয়ে নেওয়া সম্ভপর নয়।",
        "movepage-max-pages": "সর্বোচ্চ $1টি {{PLURAL:$1|উপপাতা}}স্থানান্তর করা হয়েছে এবং এর থেকে বেশি সংখ্যক পাতা সয়ংক্রিয়ভাবে স্থানান্তর সম্ভব নয়।",
index 34b49f5..e71e02f 100644 (file)
        "page_first": "یەکەمین",
        "page_last": "دوایین",
        "histlegend": "ھەڵبژاردنی جیاوازی: پیاچوونەوەکان بۆ ھەڵسەنگاندن دیاری بکە و ئینتەر یان دوگمەکەی خوارەوە لێبدە.<br />\nڕێنوێنی: '''({{int:cur}})''' = جیاوازی لەگەڵ دوایین پیاچوونەوە، '''({{int:last}})''' = جیاوازی لەگەڵ پیاچوونەوەی پێشووی، '''{{int:minoreditletter}}''' = دەستکاریی بچووک.",
-       "history-fieldset-title": "گەڕان بەدوای بەسەرداچوونەوەکان",
+       "history-fieldset-title": "پاڵاوتنی بەسەرداچوونەوەکان",
        "history-show-deleted": "تەنیا پێداچوونەوە سڕاوەکان",
        "histfirst": "کۆنترین",
        "histlast": "نوێترین",
        "recentchangeslinked-feed": "گۆڕانکارییە پەیوەندیدارەکان",
        "recentchangeslinked-toolbox": "گۆڕانکارییە پەیوەندیدارەکان",
        "recentchangeslinked-title": "گۆڕانکارییە پەیوەندیدارەکان بە \"$1\" ـەوە",
-       "recentchangeslinked-summary": "ناوی پەڕەک داخل بکە بۆ بینینی گۆڕانکارییەکانی ئەو پەڕانەی کە بەستەریان ھەیە بۆ ئەو پەڕەیە یان لەو پەڕەیەوە پەیوەست کراون. (بۆ بینینی ئەندامەکانی پۆلێک، پۆل:ناوی پۆلەکە داخل بکە). گۆڕانکارییەکانی پەڕەکانی [[Special:Watchlist|لیستی چاودێرییەکەت]] <strong>ئەستوورن</strong>.",
+       "recentchangeslinked-summary": "ناوی پەڕەک داخل بکە بۆ بینینی گۆڕانکارییەکانی ئەو پەڕانەی کە بەستەریان ھەیە بۆ ئەو پەڕەیە یان لەو پەڕەیەوە پەیوەست کراون. (بۆ بینینی ئەندامەکانی پۆلێک، {{ns:category}}:ناوی پۆلەکە داخل بکە). گۆڕانکارییەکانی پەڕەکانی [[Special:Watchlist|لیستی چاودێرییەکەت]] <strong>ئەستوورن</strong>.",
        "recentchangeslinked-page": "ناوی پەڕە:",
        "recentchangeslinked-to": "بەجێگەی ئەوە گۆڕانکارییەکانی ئەو پەڕانە نیشانبدە کە بەستەریان ھەیە بۆ پەڕەی دیاریکراو",
        "recentchanges-page-added-to-category": "[[:$1]] زیادکرا بۆ پۆل",
        "imagelinks": "بەکارھێنانی پەڕگە",
        "linkstoimage": "لەم {{PLURAL:$1|پەڕەی خوارەوە بەستەر دراوە|$1 پەڕەی خوارەوە بەستەر دراوە}} بۆ ئەم پەڕگە:",
        "linkstoimage-more": "زیاتر لە $1 {{PLURAL:$1|بەستەری لاپەڕە|بەستەری لاپەڕە}} بۆ ئەم پەڕگه.\nئەم لیستە {{PLURAL:$1|یەکەم لاپەڕەی بەستەرە|یەکەم لاپەڕە $1 بەستەرە}} بۆ تەنها یەم پەڕگە.\nهەروا [[Special:WhatLinksHere/$2|لیستی تەواو]] ئامادەی کەڵک وەرگرتنە.",
-       "nolinkstoimage": "Ú¾Û\8cÚ\86 Ù¾Û\95Ú\95Û\95Û\8cÛ\95Ú© Ù\86Û\8cÛ\8cÛ\95 Ú©Û\95 Ø¨Û\95ستÛ\95رÛ\8c Ú¾Û\95بÛ\8eت Ø¨Û\86 Ø¦Û\95Ù\85 Ù¾Û\95Ú\95Ú¯Û\95Û\8cÛ\95.",
+       "nolinkstoimage": "Ú¾Û\8cÚ\86 Ù¾Û\95Ú\95Û\95Û\8cÛ\95Ú© Ù\86Û\8cÛ\8cÛ\95 Ú©Û\95 Ø¦Û\95Ù\85 Ù¾Û\95Ú\95Ú¯Û\95Û\8cÛ\95 Ø¨Û\95کاربھÛ\8eÙ\86Û\8eت.",
        "morelinkstoimage": "[[Special:WhatLinksHere/$1|بەستەری زیاتر]] ببینە بۆ ئەم پەڕگە.",
        "linkstoimage-redirect": "$1 (ڕەوانەکەری پەڕگە) $2",
        "duplicatesoffile": "ئەم {{PLURAL:$1|پەڕگە دووبارەکرنەوەیەکی|پەڕگانە دووبارەکردنەوەی}} ئەم پەڕگەن ([[Special:FileDuplicateSearch/$2|وردەکاری زیاتر]]):",
        "uploadnewversion-linktext": "وەشانێکی نوێی ئەم پەڕگەیە بار بکە",
        "shared-repo-from": "لە لایەن $1",
        "shared-repo": "شوێنێکی هاوبەشی",
+       "shared-repo-name-wikimediacommons": "ویکیمیدیا کۆمنز",
        "upload-disallowed-here": "ناتوانی وەشانێکی نوێی ئەم پەڕگەیە بار بکەی.",
        "filerevert": "پێچەوانەکردنەوەی $1",
        "filerevert-legend": "پێچەوانەکردنەوەی پەڕگە",
index 77374a6..2559468 100644 (file)
@@ -75,7 +75,7 @@
        "june-date": "{{PLURAL:$1|1°|$1}} ghjugnu",
        "july-date": "{{PLURAL:$1|1°|$1}} lugliu",
        "august-date": "{{PLURAL:$1|1°|$1}} aostu",
-       "september-date": "{{PLURAL:$1|1°|$1}} sittembre",
+       "september-date": "{{PLURAL:$1|1°|$1}} settembre",
        "october-date": "$1 uttobre",
        "november-date": "{{PLURAL:$1|1°|$1}} nuvembre",
        "december-date": "$1 dicembre",
index 15f5ee1..e8c22b4 100644 (file)
        "content-model-css": "CSS",
        "content-json-empty-object": "Prázdný objekt",
        "content-json-empty-array": "Prázdné pole",
+       "unsupported-content-model": "<strong>Varování:</strong> Model obsahu $1 není na této wiki podporován.",
+       "unsupported-content-diff": "Rozdíly obsahů s modelem $1 nejsou podporovány.",
+       "unsupported-content-diff2": "Rozdíly mezi obsahy s modely $1 a $2 nejsou na této wiki podporovány.",
        "deprecated-self-close-category": "Stránky s neplatnými sebeuzavírajícími HTML značkami",
        "deprecated-self-close-category-desc": "Stránka obsahuje neplatné sebeuzavírající HTML značky, například <code>&lt;b/></code> nebo <code>&lt;span/></code>. Jejich chování se v zájmu konzistence se specifikací HTML5 brzy změní, proto je jejich použití ve wikitextu zastaralé.",
        "duplicate-args-warning": "<strong>Upozornění:</strong> Stránka [[:$1]] volá [[:$2]] s více než jednou hodnotou parametru „$3“. Použije se jen poslední uvedená hodnota.",
        "undo-norev": "Tuto editaci není možné vrátit, protože neexistuje nebo byla smazána.",
        "undo-nochange": "Zdá se, že editace již byla zrušena.",
        "undo-summary": "Zrušena verze $1 od uživatele [[Special:Contributions/$2|$2]] ([[User talk:$2|diskuse]])",
+       "undo-summary-anon": "Zrušena verze $1 od uživatele [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Zrušena verze $1 od skrytého uživatele",
        "cantcreateaccount-text": "Zakládání nových účtů z této IP adresy (<strong>$1</strong>) bylo zablokováno {{GENDER:$3|uživatelem|uživatelkou}} [[User:$3|$3]].\n\n$3 uvádí toto zdůvodnění: <em>$2</em>",
        "cantcreateaccount-range-text": "Zakládání nových účtů z IP adres v rozsahu <strong>$1</strong>, který obsahuje i vaši IP adresu (<strong>$4</strong>), bylo zablokováno {{GENDER:$3|uživatelem|uživatelkou}} [[User:$3|$3]].\n\n$3 uvádí toto zdůvodnění: <em>$2</em>",
        "alreadyrolled": "Nelze vrátit zpět poslední editaci [[:$1]] od uživatele [[User:$2|$2]] ([[User talk:$2|diskuse]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]), protože někdo jiný již stránku editoval nebo vrátil tuto změnu zpět.\n\nPoslední editaci této stránky {{GENDER:$3|provedl|provedla|provedl uživatel}} [[User:$3|$3]] ([[User talk:$3|diskuse]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Shrnutí editace bylo: <em>$1</em>.",
        "revertpage": "Editace uživatele „[[Special:Contributions/$2|$2]]“ ([[User talk:$2|diskuse]]) vráceny do předchozího stavu, jehož autorem je „[[User:$1|$1]]“",
+       "revertpage-anon": "Editace uživatele „[[Special:Contributions/$2|$2]]“ vráceny do předchozího stavu, jehož autorem je „[[User:$1|$1]]“",
        "revertpage-nouser": "Editace skrytého uživatele vráceny do předchozího stavu, jehož {{GENDER:$1|autorem|autorkou}} je „[[User:$1|$1]]“",
        "rollback-success": "Editace {{GENDER:$3|uživatele|uživatelky}} $1 byly vráceny na poslední verzi od {{GENDER:$4|uživatele|uživatelky}} $2.",
        "sessionfailure-title": "Chyba relace",
index cda1db8..b64046b 100644 (file)
        "checkbox-all": "Pêro",
        "checkbox-none": "Çıniyo",
        "checkbox-invert": "Dimlaşt ke",
-       "allpages": "Pêro peli",
+       "allpages": "Perri pêro",
        "nextpage": "Pela bahdoyêne ($1)",
        "prevpage": "Pela veri ($1)",
        "allpagesfrom": "Herfa kı pa liste bo:",
        "htmlform-date-placeholder": "SSSS-AA-RR",
        "htmlform-time-placeholder": "SS:DD:SS",
        "htmlform-datetime-placeholder": "SSSS-AA-RR SS:DD:SS",
+       "htmlform-title-not-creatable": "\"$1\" yew nameyê pela vıraziyiye niyo.",
        "htmlform-title-not-exists": "$1 çıni ya.",
        "htmlform-user-not-exists": "<strong>$1</strong> çıni ya.",
        "htmlform-user-not-valid": "<strong>$1</strong> hewl namey karberi niyo.",
        "log-action-filter-upload-upload": "Barkerdışo newe",
        "log-action-filter-upload-overwrite": "Anciya bar kerê",
        "log-action-filter-upload-revert": "Wegeyrayış",
+       "authmanager-retype-help": "Parola reyna raşt ke.",
        "authmanager-email-label": "E-poste",
        "authmanager-email-help": "Adresa e-posteyi",
        "authmanager-realname-label": "Nameyo raştıkên",
        "authmanager-realname-help": "Nameyê karberiyo raştıkên",
+       "authmanager-provider-temporarypassword": "Parolaya demkiye",
        "authprovider-resetpass-skip-label": "Ravêre",
        "authprovider-resetpass-skip-help": "Peysereştışê parola ra bıvêre.",
        "authform-notoken": "Tokeno kemi",
        "unlinkaccounts": "Hesabo bêgıre",
        "edit-error-short": "Xeta: $1",
        "edit-error-long": "Xeteyi:\n\n$1",
+       "specialmute": "Bêveng",
+       "specialmute-submit": "Tesdiq ke",
+       "specialmute-label-mute-email": "Nê karberi ra emailê bêvengi",
+       "mute-preferences": "Tercihê bêvengi",
        "revid": "Revizyonê $1",
        "pageid": "IDyê pela $1",
        "gotointerwiki": "{{SITENAME}} ra abırriyeno",
index e29eb53..9921c13 100644 (file)
@@ -4,7 +4,8 @@
                        "FRANCIS5091",
                        "FRANELYA",
                        "아라",
-                       "Macofe"
+                       "Macofe",
+                       "Tofeiku"
                ]
        },
        "tog-underline": "Mangagaris pioputan:",
        "editfont-monospace": "Pimato iso insir",
        "editfont-sansserif": "Pimato San-sorip",
        "editfont-serif": "Pimato Sorip",
-       "sunday": "Dautiwang",
-       "monday": "Dautonlu",
-       "tuesday": "Daumirod",
-       "wednesday": "Daumansa",
-       "thursday": "Dautadru",
-       "friday": "Daurudu",
-       "saturday": "Daukukuak",
-       "sun": "DTiw",
-       "mon": "DTon",
-       "tue": "DMir",
-       "wed": "DMad",
-       "thu": "DTad",
-       "fri": "DRud",
-       "sat": "DKua",
-       "january": "Tumilatok",
-       "february": "Tumansak",
-       "march": "Tugomot",
-       "april": "Tungiop",
-       "may_long": "Tumikat",
-       "june": "Tumahas",
-       "july": "Tumadas",
-       "august": "Tumagus",
-       "september": "Tumanom",
-       "october": "Tugumas",
-       "november": "Tumilau",
-       "december": "Tumomuhau",
+       "sunday": "Tiwang",
+       "monday": "Tontolu'",
+       "tuesday": "Mirod",
+       "wednesday": "Madsa",
+       "thursday": "Tadtaru",
+       "friday": "Kurudu",
+       "saturday": "Kukuak",
+       "sun": "Tiw",
+       "mon": "Ton",
+       "tue": "Mir",
+       "wed": "Mad",
+       "thu": "Tad",
+       "fri": "Kur",
+       "sat": "Kuk",
+       "january": "Milatok",
+       "february": "Mansak",
+       "march": "Gamot",
+       "april": "Ngiop",
+       "may_long": "Mikat",
+       "june": "Mahas",
+       "july": "Madas",
+       "august": "Magus",
+       "september": "Manom",
+       "october": "Gumas",
+       "november": "Milau",
+       "december": "Momuhau",
        "january-gen": "Milatok",
        "february-gen": "Mansak",
        "march-gen": "Gomot",
index 816839c..82826c5 100644 (file)
@@ -47,6 +47,7 @@
        "tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
        "tog-prefershttps": "Always use a secure connection while logged in",
        "tog-showrollbackconfirmation": "Show a confirmation prompt when clicking on a rollback link",
+       "tog-requireemail": "Require email for password resets",
        "underline-always": "Always",
        "underline-never": "Never",
        "underline-default": "Skin or browser default",
        "undo-norev": "The edit could not be undone because it does not exist or was deleted.",
        "undo-nochange": "The edit appears to have already been undone.",
        "undo-summary": "Undo revision $1 by [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]])",
+       "undo-summary-anon": "Undo revision $1 by [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Undo revision $1 by a hidden user",
        "cantcreateaccount-text": "Account creation from this IP address (<strong>$1</strong>) has been blocked by [[User:$3|$3]].\n\nThe reason given by $3 is <em>$2</em>",
        "cantcreateaccount-range-text": "Account creation from IP addresses in the range <strong>$1</strong>, which includes your IP address (<strong>$4</strong>), has been blocked by [[User:$3|$3]].\n\nThe reason given by $3 is <em>$2</em>",
        "prefs-help-email": "Email address is optional, but is needed for password resets, should you forget your password.",
        "prefs-help-email-others": "You can also choose to let others contact you by email through a link on your user or talk page.\nYour email address is not revealed when other users contact you.",
        "prefs-help-email-required": "Email address is required.",
+       "prefs-help-requireemail": "If checked, will only send password reset emails if the resetting person has provided both username and email for this account.",
        "prefs-info": "Basic information",
        "prefs-i18n": "Internationalisation",
        "prefs-signature": "Signature",
        "alreadyrolled": "Cannot rollback last edit of [[:$1]] by [[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nsomeone else has edited or rolled back the page already.\n\nThe last edit to the page was by [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "The edit summary was: <em>$1</em>.",
        "revertpage": "Reverted edits by [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]]) to last revision by [[User:$1|$1]]",
+       "revertpage-anon": "Reverted edits by [[Special:Contributions/$2|$2]] to last revision by [[User:$1|$1]]",
        "revertpage-nouser": "Reverted edits by a hidden user to last revision by {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Reverted edits by {{GENDER:$3|$1}};\nchanged back to last revision by {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Session failure",
        "ipblocklist-legend": "Find a blocked user",
        "blocklist-userblocks": "Hide account blocks",
        "blocklist-tempblocks": "Hide temporary blocks",
+       "blocklist-indefblocks": "Hide indefinite blocks",
        "blocklist-addressblocks": "Hide single IP blocks",
        "blocklist-type": "Type:",
        "blocklist-type-opt-all": "All",
index 1293bb1..305043e 100644 (file)
        "sessionfailure": "Badirudi saioarekin arazoren bat dagoela; ekintza hau ezeztatua izan da, saio bahiketa saihesteko neurri bezala. Mesedez, nabigatzaileko \"atzera\" botoian klik egin, hona ekarri zaituen orrialde hori berriz kargatu, eta saiatu berriz.",
        "changecontentmodel": "Aldatu orri bateko eduki eredua",
        "changecontentmodel-legend": "Aldatu eduki eredua",
-       "changecontentmodel-title-label": "Orriaren izenburua",
-       "changecontentmodel-model-label": "Eduki eredu berria",
+       "changecontentmodel-title-label": "Orriaren izenburua:",
+       "changecontentmodel-model-label": "Eduki eredu berria:",
        "changecontentmodel-reason-label": "Arrazoia:",
        "changecontentmodel-submit": "Aldatu",
        "changecontentmodel-success-title": "Eduki eredua aldatu egin da",
index f09c093..87417b3 100644 (file)
        "exif-scenetype-1": "ca de fotoğraf ker",
        "exif-customrendered-0": "Prosesê normali",
        "exif-customrendered-1": "proseso xususi",
+       "exif-customrendered-2": "HDR (oricinal nêşevekiyayo)",
+       "exif-customrendered-3": "HDR (oricinal şevekiyayo)",
+       "exif-customrendered-4": "Oricinal (seba HDRyi ra)",
+       "exif-customrendered-6": "Panorama",
+       "exif-customrendered-7": "Portre HDR",
+       "exif-customrendered-8": "Portre",
        "exif-exposuremode-0": "pozkerdışê otomatiki",
        "exif-exposuremode-1": "pozkerdışê manueli",
        "exif-exposuremode-2": "Auto bracket",
index af28ea6..8915e6c 100644 (file)
        "exif-scenetype-1": "Image photographiée directement",
        "exif-customrendered-0": "Procédé normal",
        "exif-customrendered-1": "Procédé personnalisé",
-       "exif-customrendered-2": "HDR (pas d’original enregistré)",
+       "exif-customrendered-2": "HDR (aucun original enregistré)",
        "exif-customrendered-3": "HDR (original enregistré)",
        "exif-customrendered-4": "Original (pour HDR)",
        "exif-customrendered-6": "Panorama",
index 37d33f2..df7984e 100644 (file)
        "exif-orientation": "Orientation",
        "exif-samplesperpixel": "Numero de componentes",
        "exif-planarconfiguration": "Arrangiamento del datos",
-       "exif-ycbcrsubsampling": "Ration de reduction de Y a C",
+       "exif-ycbcrsubsampling": "Ration de submonstrage de Y a C",
        "exif-ycbcrpositioning": "Positionamento Y e C",
        "exif-xresolution": "Resolution horizontal",
        "exif-yresolution": "Resolution vertical",
-       "exif-stripoffsets": "Location del datos del imagine",
+       "exif-stripoffsets": "Position del datos del imagine",
        "exif-rowsperstrip": "Numero de lineas per banda",
        "exif-stripbytecounts": "Bytes per banda comprimite",
        "exif-jpeginterchangeformat": "Position de JPEG SOI",
        "exif-webstatement": "Declaration in linea de copyright",
        "exif-originaldocumentid": "ID unic del documento original",
        "exif-licenseurl": "URL pro licentia de copyright",
-       "exif-morepermissionsurl": "Information alternative de licentia",
+       "exif-morepermissionsurl": "Information sur licentias alternative",
        "exif-attributionurl": "Si tu re-usa iste obra, per favor insere un ligamine a",
        "exif-preferredattributionname": "Si tu re-usa iste obra, per favor da recognoscentia a",
        "exif-pngfilecomment": "Commento del file PNG",
        "exif-scenetype-1": "Un imagine directemente photographiate",
        "exif-customrendered-0": "Processo normal",
        "exif-customrendered-1": "Processo personalisate",
+       "exif-customrendered-2": "HDR (original non salveguardate)",
+       "exif-customrendered-3": "HDR (original salveguardate)",
+       "exif-customrendered-4": "Original (pro HDR)",
+       "exif-customrendered-6": "Panorama",
+       "exif-customrendered-7": "Portrait HDR",
+       "exif-customrendered-8": "Portrait",
        "exif-exposuremode-0": "Exposition automatic",
        "exif-exposuremode-1": "Exposition manual",
        "exif-exposuremode-2": "Bracketing automatic",
index 96cc2a4..9c83390 100644 (file)
        "exif-photometricinterpretation-8": "CIE L*a*b*",
        "exif-photometricinterpretation-9": "CIE L*a*b* (pengkodean ICC)",
        "exif-photometricinterpretation-10": "CIE L*a*b* (pengkodean ITU)",
+       "exif-photometricinterpretation-32803": "Aturan Filter Warna",
+       "exif-photometricinterpretation-34892": "Linear raw",
        "exif-unknowndate": "Tanggal tak diketahui",
        "exif-orientation-1": "Normal",
        "exif-orientation-2": "Dibalik horisontal",
        "exif-scenetype-1": "Gambar foto langsung",
        "exif-customrendered-0": "Proses normal",
        "exif-customrendered-1": "Proses kustom",
+       "exif-customrendered-2": "HDR (aslinya tidak disimpan)",
+       "exif-customrendered-3": "HDR (aslinya disimpan)",
+       "exif-customrendered-4": "Asli (untuk HDR)",
+       "exif-customrendered-6": "Panorama",
+       "exif-customrendered-7": "Portret HDR",
+       "exif-customrendered-8": "Potret",
        "exif-exposuremode-0": "Pajanan otomatis",
        "exif-exposuremode-1": "Pajanan manual",
        "exif-exposuremode-2": "Braket otomatis",
index 44c3330..4da4ec6 100644 (file)
@@ -91,9 +91,9 @@
        "exif-imageuniqueid": "Назнака на сликата",
        "exif-gpsversionid": "Верзија на ознака за GPS податоци",
        "exif-gpslatituderef": "Северна или јужна ГШ",
-       "exif-gpslatitude": "Геог. ширина",
+       "exif-gpslatitude": "Гео. ширина",
        "exif-gpslongituderef": "Источна или западна ГД",
-       "exif-gpslongitude": "Геог. должина",
+       "exif-gpslongitude": "Гео. должина",
        "exif-gpsaltituderef": "Упатна точка за висната",
        "exif-gpsaltitude": "Височина",
        "exif-gpstimestamp": "GPS-време (атомски часовник)",
index 856cd17..3eea40f 100644 (file)
        "exif-attributionurl": "Gebruuk de volgende verwiezing bie hergebruuk van dit wark",
        "exif-preferredattributionname": "Gebruuk de volgende makersvermelding bie hergebruuk van dit wark",
        "exif-pngfilecomment": "Opmarking bie PNG-bestaand",
-       "exif-disclaimer": "Veurbehoud",
+       "exif-disclaimer": "Vöärbehold",
        "exif-contentwarning": "Waorschuwing over inhoud",
        "exif-giffilecomment": "Opmarking bie GIF-bestaand",
        "exif-intellectualgenre": "Soort onderwarp",
index f8709f3..64725b4 100644 (file)
@@ -6,7 +6,8 @@
                        "Matma Rex",
                        "Sp5uhe",
                        "Stlmch",
-                       "Railfail536"
+                       "Railfail536",
+                       "Rail"
                ]
        },
        "exif-imagewidth": "Szerokość",
index bd46ad5..5f82f91 100644 (file)
        "exif-gpsmeasuremode-2": "2-رخي ماپ",
        "exif-gpsmeasuremode-3": "3-رخي ماپ",
        "exif-gpsspeed-k": "ڪلوميٽر في ڪلاڪ",
-       "exif-gpsspeed-m": "ميل في ڪلاڪ",
+       "exif-gpsspeed-m": "ميلَ في ڪلاڪ",
        "exif-gpsspeed-n": "ناٽس",
        "exif-gpsdestdistance-k": "ڪلميٽر",
-       "exif-gpsdestdistance-m": "ميل",
-       "exif-gpsdestdistance-n": "سامونڊي ميل",
+       "exif-gpsdestdistance-m": "ميلَ",
+       "exif-gpsdestdistance-n": "سامونڊي ميلَ",
        "exif-objectcycle-p": "رڳو شام",
        "exif-dc-contributor": "ڀاڱيدار",
        "exif-dc-date": "تاريخون",
index 473a03a..bef4432 100644 (file)
@@ -7,7 +7,7 @@
                        "Uostofchuodnego"
                ]
        },
-       "exif-imagewidth": "Šyrokość",
+       "exif-imagewidth": "Szyrokość",
        "exif-imagelength": "Wysokość",
        "exif-bitspersample": "Bitůw na průbka",
        "exif-compression": "Metoda kompresyji",
        "exif-sharpness-0": "Normalno",
        "exif-sharpness-1": "Licho",
        "exif-sharpness-2": "Srogo",
-       "exif-subjectdistancerange-0": "ńyznano",
+       "exif-subjectdistancerange-0": "niyznōmŏ",
        "exif-subjectdistancerange-1": "Makro",
        "exif-subjectdistancerange-2": "widok z bliska",
        "exif-subjectdistancerange-3": "widok z daleka",
index 18c0da7..0b0f7ce 100644 (file)
@@ -74,7 +74,8 @@
                        "Amjad Khan",
                        "Ahmad252",
                        "FarsiNevis",
-                       "Moyogo"
+                       "Moyogo",
+                       "MohammadtheEditor"
                ]
        },
        "tog-underline": "خط کشیدن زیر پیوندها:",
        "revdelete-unsuppress": "حذف محدودیت‌ها در بازبینی‌های ترمیم‌شده",
        "revdelete-log": "دلیل:",
        "revdelete-submit": "اعمال بر {{PLURAL:$1|نسخهٔ|نسخه‌های}} انتخاب شده",
-       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø±Ù\88زآÙ\85د شد.",
+       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø¨Ø±Ù\88ز شد.",
        "revdelete-failure": "'''پیدایی نسخه‌ها قابل به روز کردن نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
index 33cddd8..994b1e1 100644 (file)
        "passwordreset-ignored": "Salasanan palauttamista ei käsitelty. Ehkä tarjoajaa ei ollut määritetty?",
        "passwordreset-invalidemail": "Virheellinen sähköpostiosoite",
        "passwordreset-nodata": "Käyttäjätunnusta ja salasanaa ei annettu",
-       "changeemail": "Muuta tai poista E-posti atressi",
+       "changeemail": "Muuta tai poista sähköpostiosoite",
        "changeemail-header": "Täydennä tämä lomake, jolla voit muuttaa sähköpostiosoitettasi. Jos haluat poistaa sähköpostiosoitteesi kokonaan tunnuksesi yhteydestä, älä kirjoita uudeksi osoitteeksi mitään vaan jätä se tyhjäksi.",
        "changeemail-no-info": "Tämän sivun käyttö edellyttää sisäänkirjautumista.",
        "changeemail-oldemail": "Nykyinen sähköpostiosoite:",
        "updated": "(Päivitetty)",
        "note": "'''Huomautus:'''",
        "previewnote": "<strong>Tämä on vasta sivun esikatselu.</strong>\nTekemiäsi muutoksia ei ole vielä tallennettu.",
-       "continue-editing": "Siiry mookkauskenttään",
+       "continue-editing": "Siirry muokkauskenttään",
        "previewconflict": "Tämä esikatselu näyttää miltä muokkausalueella oleva teksti näyttää tallennettuna.",
        "session_fail_preview": "Muokkaustasi ei voitu tallentaa, koska istuntosi tiedot ovat kadonneet.\n\nSaatat olla kirjautunut ulos. '''Varmista, että olet edelleen kirjautunut sisään ja yritä uudelleen'''. Jos ongelma ei katoa, yritä [[Special:UserLogout|kirjautua ulos]] ja takaisin sisään, ja varmista, että selaimesi sallii evästeet tältä sivustolta.",
        "session_fail_preview_html": "Valitettavasti muokkaustasi ei voitu käsitellä istunnon tietojen katoamisen vuoksi.\n\n<em>Koska {{GRAMMAR:inessive|{{SITENAME}}}} on käytössä suodattamaton HTML-koodi, esikatselu on piilotettu JavaScript-hyökkäyksien torjumiseksi</em>\n\n<strong>Jos tämä on oikea muokkausyritys, yritä uudelleen.</strong> Jos ongelma ei katoa, yritä [[Special:UserLogout|kirjautua ulos]] ja takaisin sisään. Tarkista myös, että selaimesi sallii evästeet tältä sivustolta.",
        "prefs-watchlist-managetokens": "Hallitse avaimia",
        "prefs-misc": "Muut",
        "prefs-resetpass": "Muuta salasana",
-       "prefs-changeemail": "Muuta tai poista E-posti atressi",
+       "prefs-changeemail": "Muuta tai poista sähköpostiosoite",
        "prefs-setemail": "Aseta sähköpostiosoite",
        "prefs-email": "Sähköpostiasetukset",
        "prefs-rendering": "Ulkoasu",
index 71e9e88..5ce9fd5 100644 (file)
        "botpasswords-label-delete": "Ota poies",
        "resetpass-submit-cancel": "Lopeta",
        "passwordreset-email": "E-postin atressi:",
+       "changeemail": "Muuta tai poista E-postin atressi",
        "changeemail-newemail": "Uusi E-postin atressi:",
        "bold_sample": "Lihava teksti",
        "bold_tip": "Lihava teksti",
        "linkstoimage": "{{PLURAL:$1|Seuraava sivu|Seuraavat $1 sivua}} käytthävät tätä fiilhiä:",
        "nolinkstoimage": "Ei ole yhtään sivua joka käyttää tätä fiilhiä.",
        "sharedupload-desc-here": "Tämä fiili on jaettu kohtheesta $1 ja muut prujektit saattavat käyttää sitä.\nTiot [$2 fiilin kuvvaussivulta] näkyvät tässä alla.",
+       "shared-repo-name-wikimediacommons": "Wikimeetia Commons",
        "filedelete": "Ota poies $1",
        "filedelete-legend": "Ota poies fiili",
        "filedelete-submit": "Ota poies",
        "istemplate": "sisäletty mallina",
        "isimage": "linkki fiilhiin",
        "whatlinkshere-prev": "← {{PLURAL:$1|eelinen sivu|$1 eelistä sivua}}",
-       "whatlinkshere-next": "{{PLURAL:$1|seuraava sivu|$1 seuraava sivu}} →",
+       "whatlinkshere-next": "{{PLURAL:$1|seuraava sivu|$1 seuraavaa sivua}} →",
        "whatlinkshere-links": "linkit",
        "whatlinkshere-hideredirs": "$1 ohjaukset",
        "whatlinkshere-hidetrans": "$1 mallin inklyteerinkiä",
index 73a7bc4..41b2293 100644 (file)
        "filerenameerror": "Impossible de renommer le fichier « $1 » en « $2 ».",
        "filedeleteerror": "Impossible de supprimer le fichier « $1 ».",
        "directorycreateerror": "Impossible de créer le répertoire « $1 ».",
-       "directoryreadonlyerror": "Le répertoire « $1 » est en lecture seule.",
-       "directorynotreadableerror": "Le répertoire « $1 » n’est pas lisible.",
+       "directoryreadonlyerror": "Le répertoire « $1 » est en lecture seule.",
+       "directorynotreadableerror": "Le répertoire « $1 » n’est pas lisible.",
        "filenotfound": "Impossible de trouver le fichier « $1 ».",
        "unexpected": "Valeur inattendue : « $1 » = « $2 ».",
        "formerror": "Erreur : impossible de soumettre le formulaire.",
        "badarticleerror": "Cette action ne peut pas être effectuée sur cette page.",
        "cannotdelete": "Impossible de supprimer la page ou le fichier « $1 ».\nLa suppression a peut-être déjà été effectuée par quelqu’un d’autre.",
        "cannotdelete-title": "Impossible de supprimer la page « $1 »",
-       "delete-scheduled": "La page « $1 » est programmée pour être supprimée.\nVeuillez patienter.",
+       "delete-scheduled": "La page « $1 » est programmée pour être supprimée.\nVeuillez patienter.",
        "delete-hook-aborted": "Suppression annulée par une extension.\nAucune explication n’a été fournie.",
-       "no-null-revision": "Impossible de créer une nouvelle révision vide pour la page « $1 »",
+       "no-null-revision": "Impossible de créer une nouvelle révision vide pour la page « $1 »",
        "badtitle": "Mauvais titre",
        "badtitletext": "Le titre de la page demandée est non valide, vide, ou mal formé s’il s’agit d’un titre inter-langue ou inter-projet.\nIl contient peut-être un ou plusieurs caractères qui ne peuvent pas être utilisés dans les titres.",
        "title-invalid-empty": "Le titre de la page demandée est vide ou contient seulement le nom d’un espace de noms.",
        "virus-scanfailed": "échec de l’analyse (code $1)",
        "virus-unknownscanner": "antivirus inconnu :",
        "logouttext": "<strong>Vous êtes à présent déconnecté{{GENDER:||e|(e)}}.</strong>\n\nNotez que certaines pages peuvent être encore affichées comme si vous étiez toujours connecté, jusqu’à ce que vous effaciez le cache de votre navigateur.",
-       "logging-out-notify": "Vous allez être déconnecté, veuillez attendre.",
-       "logout-failed": "Impossible de se déconnecter maintenant : $1",
+       "logging-out-notify": "Vous allez être déconnecté, veuillez patienter.",
+       "logout-failed": "Impossible de se déconnecter maintenant: $1",
        "cannotlogoutnow-title": "Impossible de se déconnecter maintenant",
        "cannotlogoutnow-text": "La déconnexion n’est pas possible en utilisant $1.",
        "welcomeuser": "Bienvenue, $1 !",
        "badretype": "Les mots de passe que vous avez saisis ne correspondent pas.",
        "usernameinprogress": "Une création de compte pour ce nom d’utilisateur est déjà en cours.\nVeuillez patienter.",
        "userexists": "Le nom d’utilisateur saisi est déjà utilisé.\nVeuillez choisir un nom différent.",
-       "createacct-normalization": "Votre nom d’utilisateur sera modifié en « $2 » à cause de restrictions techniques.",
+       "createacct-normalization": "Votre nom d’utilisateur sera modifié en « $2 » à cause de restrictions techniques.",
        "loginerror": "Erreur de connexion",
        "createacct-error": "Erreur lors de la création du compte",
        "createaccounterror": "Impossible de créer le compte : $1",
        "noname": "Vous n’avez pas saisi un nom d’utilisateur valide.",
        "loginsuccesstitle": "Connecté",
        "loginsuccess": "<strong>Vous êtes maintenant connecté{{GENDER:$1||e|(e)}} à {{SITENAME}} en tant que « $1 ».</strong>",
-       "nosuchuser": "L’utilisateur « $1 » n’existe pas.\nLes noms d’utilisateur sont sensibles à la casse.\nVérifiez l’orthographe, ou [[Special:CreateAccount|créez un nouveau compte]].",
+       "nosuchuser": "L’utilisateur « $1 » n’existe pas.\nLes noms d’utilisateur sont sensibles à la casse.\nVérifiez l’orthographe, ou [[Special:CreateAccount|créez un nouveau compte]].",
        "nosuchusershort": "Il n’y a pas de contributeur avec le nom « $1 ».\nVeuillez vérifier l’orthographe.",
        "nouserspecified": "Vous devez saisir un nom d’utilisateur.",
        "login-userblocked": "{{GENDER:$1|Cet utilisateur|Cette utilisatrice}} est bloqué{{GENDER:$1||e}}. La connexion n’est pas autorisée.",
        "password-login-forbidden": "L’utilisation de ce nom d’utilisateur ou de ce mot de passe a été interdite.",
        "mailmypassword": "Réinitialiser le mot de passe",
        "passwordremindertitle": "Nouveau mot de passe temporaire pour {{SITENAME}}",
-       "passwordremindertext": "Quelqu’un (depuis l’adresse IP $1) a demandé un nouveau mot de\npasse pour {{SITENAME}} ($4). Un mot de passe temporaire pour l’utilisateur\n« $2 » a été créé et est « $3 ». Si cela était votre intention,\nvous devrez vous connecter et choisir un nouveau mot de passe.\nVotre mot de passe temporaire expirera dans $5 jour{{PLURAL:$5||s}}.\n\nSi vous n’êtes pas l’auteur de cette demande, ou si vous vous avez retrouvé votre mot de passe et ne souhaitez plus en changer, vous pouvez ignorer ce message\net continuer à utiliser votre ancien mot de passe.",
+       "passwordremindertext": "Quelqu’un (depuis l’adresse IP $1) a demandé un nouveau mot de\npasse pour {{SITENAME}} ($4). Un mot de passe temporaire pour l’utilisateur\n« $2 » a été créé et défini comme « $3 ». Si c’était bien votre intention,\nvous devrez vous connecter et choisir un nouveau mot de passe.\nVotre mot de passe temporaire expirera dans $5 jour{{PLURAL:$5||s}}.\n\nSi vous n’êtes pas l’auteur de cette demande, ou si vous vous avez retrouvé votre mot de passe et ne souhaitez plus en changer, vous pouvez ignorer ce message\net continuer à utiliser votre ancien mot de passe.",
        "noemail": "Aucune adresse de courriel n’a été enregistrée pour l’utilisat{{GENDER:$1|eur|rice}} « $1 ».",
        "noemailcreate": "Vous devez fournir une adresse de courriel valide",
        "passwordsent": "Un nouveau mot de passe a été envoyé à l’adresse de courriel de l’utilisat{{GENDER:$1|eur|rice}} « $1 ».\nVeuillez vous reconnecter après l’avoir reçu.",
        "botpasswords-label-grants": "Droits applicables :",
        "botpasswords-help-grants": "Les autorisations permettent d’accéder aux droits déjà accordés à votre compte utilisateur. Activer une autorisation ici ne fournit l’accès à aucun droit que votre compte utilisateur n’aurait pas par ailleurs. Voyez le [[Special:ListGrants|tableau des autorisations]] pour plus d’informations.",
        "botpasswords-label-grants-column": "Accordé",
-       "botpasswords-bad-appid": "Le nom de robot « $1 » n’est pas valide.",
-       "botpasswords-insert-failed": "Échec de l’ajout du nom de robot « $1 ». A-t-il déjà été ajouté ?",
-       "botpasswords-update-failed": "Échec à la mise à jour du nom de robot « $1 ». A-t-il déjà été supprimé ?",
+       "botpasswords-bad-appid": "Le nom de robot « $1 » n’est pas valide.",
+       "botpasswords-insert-failed": "Échec de l’ajout du nom de robot « $1 ». A-t-il déjà été ajouté ?",
+       "botpasswords-update-failed": "Échec à la mise à jour du nom de robot « $1 ». A-t-il déjà été supprimé ?",
        "botpasswords-created-title": "Mot de passe de robots créé",
        "botpasswords-created-body": "Le mot de passe pour le robot « $1 » de l’{{GENDER:$2|utilisateur|utilisatrice}} « $2 » a été créé.",
        "botpasswords-updated-title": "Mot de passe de robots mis à jour",
        "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 »).",
-       "botpasswords-not-exist": "L’{{GENDER:$1|utilisateur|utilisatrice}} « $1 » n’a pas de mot de passe de robot nommé « $2 ».",
-       "botpasswords-needs-reset": "Le mot de passe du robot de nom « $2 » de l’utilisat{{GENDER:$1|eur|rice}} « $1 » doit être réinitialisé.",
+       "botpasswords-invalid-name": "Le nom d’utilisateur spécifié ne contient pas de séparateur de mot de passe de robots (« $1 »).",
+       "botpasswords-not-exist": "L’utilisat{{GENDER:$1|eur|rice}} « $1 » n’a pas de mot de passe de robot nommé « $2 ».",
+       "botpasswords-needs-reset": "Le mot de passe du robot nommé « $2 » de l’utilisat{{GENDER:$1|eur|rice}} « $1 » doit être réinitialisé.",
        "botpasswords-locked": "Vous ne pouvez pas vous connecter avec un mot de passe de robot, car votre compte est bloqué.",
        "resetpass_forbidden": "Les mots de passe ne peuvent pas être changés",
        "resetpass_forbidden-reason": "Les mots de passe ne peuvent pas être modifiés : $1",
        "resetpass-temp-password": "Mot de passe temporaire :",
        "resetpass-abort-generic": "La modification du mot de passe a été annulée par une extension.",
        "resetpass-expired": "Votre mot de passe a expiré. Veuillez en fournir un nouveau pour vous connecter.",
-       "resetpass-expired-soft": "Votre mot de passe a expiré, et doit être modifié. Veuillez en choisir un nouveau maintenant ou cliquer sur « {{int:authprovider-resetpass-skip-label}} » pour le faire plus tard.",
-       "resetpass-validity": "Votre mot de passe est non valide : $1\n\nVeuillez entrer un nouveau mot de passe pour vous connecter.",
-       "resetpass-validity-soft": "Votre mot de passe n’est pas valide : $1\n\nVeuillez choisir un nouveau mot de passe maintenant, ou cliquez sur « {{int:authprovider-resetpass-skip-label}} » pour le modifier plus tard.",
+       "resetpass-expired-soft": "Votre mot de passe a expiré, et doit être modifié. Veuillez en choisir un nouveau maintenant ou cliquer sur « {{int:authprovider-resetpass-skip-label}} » pour le faire plus tard.",
+       "resetpass-validity": "Votre mot de passe est non valide: $1\n\nVeuillez entrer un nouveau mot de passe pour vous connecter.",
+       "resetpass-validity-soft": "Votre mot de passe n’est pas valide : $1\n\nVeuillez choisir un nouveau mot de passe maintenant, ou cliquez sur « {{int:authprovider-resetpass-skip-label}} » pour le modifier plus tard.",
        "passwordreset": "Réinitialisation du mot de passe",
        "passwordreset-text-one": "Remplissez ce formulaire pour réinitialiser votre mot de passe.",
        "passwordreset-text-many": "{{PLURAL:$1|Remplissez un des champs pour recevoir un mot de passe temporaire par courriel.}}",
        "preview": "Prévisualisation",
        "showpreview": "Prévisualiser",
        "showdiff": "Voir les modifications",
-       "blankarticle": "<strong>Attention :</strong> la page que vous créez est vide.\nSi vous cliquez de nouveau sur « $1 », la page sera créée sans aucun contenu.",
+       "blankarticle": "<strong>Attention :</strong> la page que vous créez est vide.\nSi vous cliquez de nouveau sur « $1 », la page sera créée sans aucun contenu.",
        "anoneditwarning": "<strong>Attention :</strong> vous n’êtes pas connecté(e). Votre adresse IP sera visible de tout le monde si vous faites des modifications. Si vous <strong>[$1 vous connectez]</strong> ou <strong>[$2 créez un compte]</strong>, vos modifications seront attribuées à votre propre nom d’utilisateur(rice) et vous aurez d’autres avantages.",
        "anonpreviewwarning": "<em>Vous n’êtes pas connecté(e). Sauvegarder enregistrera votre adresse IP dans l’historique des modifications de la page.</em>",
        "missingsummary": "<strong>Rappel :</strong> vous n’avez pas encore fourni le résumé de votre modification.\nSi vous cliquez de nouveau sur le bouton « $1 », vos modifications seront sauvegardées sans résumé.",
-       "selfredirect": "<strong>Attention :</strong> vous êtes en train de rediriger la page vers elle-même.\nIl se peut que vous ayez spécifié la mauvaise cible pour la redirection, ou que vous modifiez peut-être la mauvaise page.\nSi vous cliquez de nouveau sur « $1 », la redirection sera tout de même créée.",
+       "selfredirect": "<strong>Attention :</strong> vous êtes en train de rediriger la page vers elle-même.\nIl se peut que vous ayez spécifié la mauvaise cible pour la redirection, ou que vous modifiez peut-être la mauvaise page.\nSi vous cliquez de nouveau sur « $1 », la redirection sera tout de même créée.",
        "missingcommenttext": "Veuillez entrer un commentaire.",
-       "missingcommentheader": "<strong>Rappel :</strong> vous n’avez pas fourni de sujet pour ce commentaire.\nSi vous cliquez de nouveau sur « {{int:Savearticle}} », votre modification sera enregistrée sans sujet.",
+       "missingcommentheader": "<strong>Rappel :</strong> vous n’avez pas fourni de sujet pour ce commentaire.\nSi vous cliquez de nouveau sur « $1 », votre modification sera enregistrée sans sujet.",
        "summary-preview": "Aperçu du résumé de modification :",
        "subject-preview": "Aperçu du sujet :",
        "previewerrortext": "Une erreur s’est produite lors de la tentative de prévisualisation de vos modifications.",
        "blockedtitle": "L’utilisateur est bloqué.",
-       "blocked-email-user": "<strong>Votre nom d’utilisateur a été bloqué pour l’envoi de courriels. Vous pouvez toujours modifier d’autres pages sur ce wiki.</strong> Vous pouvez voir tous les détails du blocage sur [[Special:MyContributions|contributions du compte]].\n\nCe blocage a été fait par $1.\n\nLe motif fourni est <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Cible souhaitée du blocage : $7\n* ID du blocage #$5",
-       "blockedtext-partial": "<strong>Votre nom d’utilisateur ou votre adresse IP a été bloqué pour effectuer des modifications sur cette page. Vous pouvez toujours modifier d’autres pages sur ce wiki.</strong> Vous pouvez voir tous les détails sur ce blocage sur [[Special:MyContributions|contributions du compte]].\n\nLe blocage a été effectué par $1.\n\nLe motif fourni est <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Cible souhaitée du blocage : $7\n* ID du blocage #$5",
-       "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité ne vous a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
-       "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+       "blocked-email-user": "<strong>Votre nom d’utilisateur a été bloqué pour l’envoi de courriels. Vous pouvez toujours modifier d’autres pages sur ce wiki.</strong> Vous pouvez voir tous les détails du blocage sur [[Special:MyContributions|contributions du compte]].\n\nCe blocage a été fait par $1.\n\nLe motif fourni est <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Cible souhaitée du blocage : $7\n* ID du blocage nº $5",
+       "blockedtext-partial": "<strong>Votre nom d’utilisateur ou votre adresse IP a été bloqué pour effectuer des modifications sur cette page. Vous pouvez toujours modifier d’autres pages sur ce wiki.</strong> Vous pouvez voir tous les détails sur ce blocage sur [[Special:MyContributions|contributions du compte]].\n\nLe blocage a été effectué par $1.\n\nLe motif fourni est <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Cible souhaitée du blocage : $7\n* ID du blocage nº $5",
+       "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité ne vous a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+       "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
        "systemblockedtext": "Votre nom d'utilisateur ou votre adresse IP ont été bloqués automatiquement par MediaWiki.\nLa raison donnée est la suivante:\n\n: <em>$2</em>.\n\n* Le début du blocage: $8\n* Expiration du délai de blocage: $6\n* Elément concerné: $7\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
        "blockednoreason": "aucune raison donnée",
-       "blockedtext-composite": "<strong>Votre nom d'utilisateur ou votre adresse IP ont été bloqués.</strong>\n\nLa raison invoquées est :\n\n:<em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage le plus long : $6\n\n* $5\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chaque demande que vous ferez.",
-       "blockedtext-composite-ids": "ID de bloc pertinents : $1 (votre adresse IP peut aussi être en liste noire)",
+       "blockedtext-composite": "<strong>Votre nom d'utilisateur ou votre adresse IP ont été bloqués.</strong>\n\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage le plus long : $6\n* $5\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chaque demande que vous ferez.",
+       "blockedtext-composite-ids": "ID de bloc pertinents: $1 (votre adresse IP peut aussi être en liste noire)",
        "blockedtext-composite-no-ids": "Votre adresse IP apparaît dans plusieurs listes noires",
        "blockedtext-composite-reason": "Il existe plusieurs blocages sur votre compte et/ou votre adresse IP",
        "whitelistedittext": "Vous devez vous $1 pour avoir la permission de modifier le contenu.",
        "newarticletext": "Vous avez suivi un lien vers une page qui n’existe pas encore. \nAfin de créer cette page, entrez votre texte dans la boîte ci-après (vous pouvez consulter [$1 la page d’aide] pour plus d’informations). \nSi vous êtes arrivé{{GENDER:||e}} ici par erreur, cliquez sur le bouton <strong>Retour</strong> de votre navigateur.",
        "anontalkpagetext": "----\n<em>Vous êtes sur la page de discussion d’un utilisateur anonyme qui n’a pas encore créé de compte ou qui n’en utilise pas</em>.\nPour cette raison, nous devons utiliser son adresse IP pour l'identifier.\nUne telle adresse IP peut être partagée par plusieurs utilisateurs.\nSi vous êtes un{{GENDER:||e|}} utilisat{{GENDER:|eur|rice|eur}} anonyme et si vous constatez que des commentaires qui ne vous concernent pas vous ont été adressés, vous pouvez [[Special:CreateAccount|créer un compte]] ou [[Special:UserLogin|vous connecter]] afin d’éviter toute confusion future avec d’autres contributeurs anonymes.",
        "noarticletext": "Il n’y a pour l’instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|lancer une recherche sur ce titre]] dans les autres pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les opérations liées]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} créer cette page]</span>.",
-       "noarticletext-nopermission": "Il n'y a pour l'instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|faire une recherche sur ce titre]] dans les autres pages,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les journaux associés]</span>, mais vous n'avez pas la permission de créer cette page.",
-       "missing-revision": "La révision nº $1 de la page intitulée « {{FULLPAGENAME}} » n’existe pas.\n\nCela survient en général en suivant un lien historique désuet vers une page qui a été supprimée.\nVous pouvez trouver plus de détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
+       "noarticletext-nopermission": "Il n’y a pour l’instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|faire une recherche sur ce titre]] dans les autres pages,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les journaux associés]</span>, mais vous n’avez pas la permission de créer cette page.",
+       "missing-revision": "La révision nº $1 de la page intitulée « {{FULLPAGENAME}} » n’existe pas.\n\nCela survient en général en suivant un lien historique désuet vers une page qui a été supprimée.\nVous pouvez trouver plus de détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
        "userpage-userdoesnotexist": "Le compte utilisateur « <nowiki>$1</nowiki> » n’est pas enregistré. Veuillez vérifier que vous voulez créer cette page.",
        "userpage-userdoesnotexist-view": "Le compte utilisateur « $1 » n'est pas enregistré.",
-       "blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est affichée ci-dessous pour référence :",
+       "blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est affichée ci-dessous pour référence:",
        "clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) \n* <strong>Google Chrome :</strong> appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) \n* <strong>Internet Explorer :</strong> maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> \n* <strong>Opera :</strong> allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d’exploration → Images et fichiers en cache</em>.",
        "usercssyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille CSS avant de l’enregistrer.",
-       "userjsonyoucanpreview": "<strong>Conseil :</strong> Utiliser le bouton « {{int:showpreview}} » pour tester votre nouveau JSON avant enregistrement.",
+       "userjsonyoucanpreview": "<strong>Conseil :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouveau JSON avant enregistrement.",
        "userjsyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille JavaScript avant de l’enregistrer.",
        "usercsspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser votre propre feuille CSS. \nElle n’a pas encore été enregistrée !</strong>",
        "userjsonpreview": "<strong>Rappelez-vous que vous êtes seulement en train de tester/voir un aperçu de votre configuration utilisateur JSON.\nElle n’a pas encore été enregistrée !</strong>",
        "sitecsspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser cette feuille de style. \nElle n’a pas encore été enregistrée !</strong>",
        "sitejsonpreview": "<strong>Souvenez-vous que vous ne faites que regarder un aperçu de cette configuration JSON.\nElle n’a pas encore été enregistrée !</strong>",
        "sitejspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser ce code JavaScript. \nIl n’a pas encore été enregistré !</strong>",
-       "userinvalidconfigtitle": "<strong>Attention :</strong> il n’existe pas d’habillage « $1 ». \nLes pages personnelles avec extensions .css, .json et .js utilisent des titres en minuscules, par exemple {{ns:user}}:Foo/vector.css et non {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Attention :</strong> il n’existe pas d’habillage « $1 ».\nLes pages personnelles avec extensions .css, .json et .js utilisent des titres en minuscules, par exemple {{ns:user}}:Foo/vector.css et non {{ns:user}}:Foo/Vector.css.",
        "updated": "(Mis à jour)",
        "note": "<strong>Note :</strong>",
        "previewnote": "<strong>Rappelez-vous que ce n’est qu’une prévisualisation.</strong>\nVos modifications n’ont pas encore été enregistrées !",
        "nocreate-loggedin": "Vous n'avez pas la permission de créer de nouvelles pages.",
        "sectioneditnotsupported-title": "Modification de section non prise en charge",
        "sectioneditnotsupported-text": "La modification d’une section n’est pas prise en charge pour cette page.",
-       "modeleditnotsupported-title": "Modification non supportée",
-       "modeleditnotsupported-text": "La modification n’est pas supportée pour le modèle de contenu $1.",
+       "modeleditnotsupported-title": "Modification non prise en charge",
+       "modeleditnotsupported-text": "La modification n’est pas prise en charge pour le modèle de contenu $1.",
        "permissionserrors": "Erreur de permissions",
        "permissionserrorstext": "Vous n'avez pas la permission d'effectuer l'opération demandée pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
        "permissionserrorstext-withaction": "Vous ne pouvez pas $2, pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
        "defaultmessagetext": "Message par défaut",
        "content-failed-to-parse": "Échec de l’analyse syntaxique du contenu de $2 pour le modèle $1 : $3",
        "invalid-content-data": "Données du contenu non valides",
-       "content-not-allowed-here": "Le contenu « $1 » n’est pas autorisé sur la page [[:$2]] dans l’emplacement « $3 »",
-       "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.",
+       "content-not-allowed-here": "Le contenu « $1 » n’est pas autorisé sur la page [[:$2]] dans l’emplacement « $3 »",
+       "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",
        "content-model-json": "JSON",
        "content-json-empty-object": "Objet vide",
        "content-json-empty-array": "Tableau vide",
-       "unsupported-content-model": "<strong>Attention :</strong> Le modèle de contenu $1 n’est pas supporté sur ce wiki.",
+       "unsupported-content-model": "<strong>Attention :</strong> le modèle de contenu $1 n’est pas pris en charge sur ce wiki.",
        "unsupported-content-diff": "Les diffs ne sont pas supportés pour le modèle de contenu $1.",
        "unsupported-content-diff2": "Les diffs entre les modèles de contenu $1 et $2 ne sont pas supportés sur ce wiki.",
        "deprecated-self-close-category": "Pages utilisant des balises HTML auto-fermantes non valides",
        "undo-norev": "La modification n’a pas pu être défaite parce qu’elle est inexistante ou qu’elle a été supprimée.",
        "undo-nochange": "Il semblerait que la modification ait déjà été annulée.",
        "undo-summary": "Annulation des modifications $1 de [[Special:Contributions/$2|$2]] ([[User talk:$2|discussion]])",
+       "undo-summary-anon": "Annuler la modification $1 de [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Annuler la révision $1 par un utilisateur masqué",
        "cantcreateaccount-text": "La création de compte depuis cette adresse IP (<strong>$1</strong>) a été bloquée par [[User:$3|$3]]. \n\nLa raison donnée par $3 était : <em>$2</em>",
        "cantcreateaccount-range-text": "La création de compte depuis les adresses IP de la plage <strong>$1</strong>, où se trouve votre adresse IP (<strong>$4</strong>), a été bloquée par [[User:$3|$3]].\n\nLe motif fourni par $3 est <em>$2</em>",
        "diff-paragraph-moved-toold": "Paragraphe déplacé. Cliquer pour accéder à l’ancien emplacement.",
        "difference-missing-revision": "{{PLURAL:$2|Une révision|$2 révisions}} de cette différence ($1) {{PLURAL:$2|n’a pas été trouvée|n’ont pas été trouvées}}.\n\nCela survient en général en suivant un lien de différence désuet vers une page qui a été supprimée.\nVous pouvez trouver des détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
        "searchresults": "Résultats de la recherche",
-       "search-filter-title-prefix": "Recherche seulement les pages dont le titre commence par « $1 »",
+       "search-filter-title-prefix": "Recherche seulement les pages dont le titre commence par « $1 »",
        "search-filter-title-prefix-reset": "Rechercher toutes les pages",
        "searchresults-title": "Résultats de recherche pour « $1 »",
        "titlematches": "Correspondances dans les titres des pages",
        "shown-title": "Afficher $1 résultat{{PLURAL:$1||s}} par page",
        "viewprevnext": "Voir ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "<strong>Il existe une page nommée « [[:$1]] » sur ce wiki.</strong> {{PLURAL:$2|0=|Voyez également les autres résultats de votre recherche.}}",
-       "searchmenu-new": "<strong>Créer la page « [[:$1|$1]] » sur ce wiki !</strong> {{PLURAL:$2|0=Voyez également la page trouvée avec votre recherche.|Voyez également les résultats de votre recherche.}}",
+       "searchmenu-new": "<strong>Créer la page « [[:$1|$1]] » sur ce wiki !</strong> {{PLURAL:$2|0=Voyez également la page trouvée avec votre recherche.|Voyez également les résultats de votre recherche.}}",
        "searchprofile-articles": "Pages de contenu",
        "searchprofile-images": "Multimédia",
        "searchprofile-everything": "Tout",
        "prefs-tokenwatchlist": "Jeton",
        "prefs-diffs": "Différences",
        "prefs-help-prefershttps": "Cette préférence sera effective lors de votre prochaine connexion.",
-       "prefswarning-warning": "Vous avez effectué des modifications dans vos préférences qui n’ont pas encore été enregistrées.\nSi vous quittez cette page sans cliquer sur « $1 », vos préférences ne seront pas mises à jour.",
+       "prefswarning-warning": "Vous avez effectué des modifications dans vos préférences qui n’ont pas encore été enregistrées.\nSi vous quittez cette page sans cliquer sur « $1 », vos préférences ne seront pas mises à jour.",
        "prefs-tabs-navigation-hint": "Astuce : Vous pouvez utiliser les flèches de gauche et de droite pour naviguer entre les onglets.",
        "userrights": "Droits des utilisateurs",
        "userrights-lookup-user": "Sélectionner un utilisateur",
        "userrights-expiry-options": "1 jour:1 day,1 semaine:1 week,1 mois:1 month,3 mois:3 montghs,6 mois:6 month,1 an:1 year",
        "userrights-invalid-expiry": "La date d'expiration pour le groupe « $1 » n'est pas valide.",
        "userrights-expiry-in-past": "La date d'expiration pour le groupe « $1 » est dépassée.",
-       "userrights-cannot-shorten-expiry": "Vous ne pouvez pas raccourcir la durée d’expiration de l’appartenance au groupe « $1 ». Seuls les utilisateurs disposant de l’autorisation d’ajouter et de supprimer ce groupe peuvent raccourcir les durées d’expiration.",
+       "userrights-cannot-shorten-expiry": "Vous ne pouvez pas raccourcir la durée d’expiration de l’appartenance au groupe « $1 ». Seuls les utilisateurs disposant de l’autorisation d’ajouter et de supprimer ce groupe peuvent raccourcir les durées d’expiration.",
        "userrights-conflict": "Conflit de modification des droits utilisateur ! Veuillez relire et confirmer vos modifications.",
        "group": "Groupe :",
        "group-user": "Utilisateurs",
        "right-applychangetags": "Appliquer [[Special:Tags|les balises]] avec ses propres modifications",
        "right-changetags": "Ajouter et supprimer de façon arbitraire [[Special:Tags|des balises]] sur des révisions individuelles et des entrées de journal",
        "right-deletechangetags": "Supprimer des [[Special:Tags|balises]] de la base de données",
-       "grant-generic": "ensemble de droits « $1 »",
+       "grant-generic": "ensemble de droits « $1 »",
        "grant-group-page-interaction": "Interagir avec des pages",
        "grant-group-file-interaction": "Interagir avec des médias",
        "grant-group-watchlist-interaction": "Interagir avec votre liste de suivi",
        "action-bigdelete": "supprimer des pages avec de grands historiques",
        "action-blockemail": "empêcher un utilisateur d’envoyer des courriels",
        "action-bot": "être traité comme un processus automatisé",
-       "action-editprotected": "modifier les pages protégées comme « {{int:protect-level-sysop}} »",
+       "action-editprotected": "modifier les pages protégées comme « {{int:protect-level-sysop}} »",
        "action-editsemiprotected": "modifier des pages protégées avec le statut « {{int:protect-level-autoconfirmed}} »",
        "action-editinterface": "modifier l’interface utilisateur",
        "action-editusercss": "modifier les fichiers CSS d’autres utilisateurs",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nouveaux arrivants",
        "rcfilters-filter-user-experience-level-newcomer-description": "Éditeurs connectés ayant fait moins de 10 modifications ou ayant moins de 4 jours d’activité.",
        "rcfilters-filter-user-experience-level-learner-label": "Apprentis",
-       "rcfilters-filter-user-experience-level-learner-description": "Éditeurs connectés dont l’expérience se situe entre  « Nouveaux arrivants » et  « Utilisateurs expérimentés ».",
+       "rcfilters-filter-user-experience-level-learner-description": "Éditeurs connectés dont l’expérience se situe entre  « Nouveaux arrivants » et  « Utilisateurs expérimentés ».",
        "rcfilters-filter-user-experience-level-experienced-label": "Utilisateurs expérimentés",
        "rcfilters-filter-user-experience-level-experienced-description": "Éditeurs connectés avec plus de 500 modifications et 30 jours d’activité.",
        "rcfilters-filtergroup-automated": "Contributions automatisées",
        "rcfilters-filter-categorization-description": "Enregistrements de pages ajoutées ou supprimées des catégories.",
        "rcfilters-filter-logactions-label": "Actions tracées",
        "rcfilters-filter-logactions-description": "Actions d’administration, créations de compte, suppression de pages, téléchargements…",
-       "rcfilters-hideminor-conflicts-typeofchange-global": "Le filtre « Modifications mineures » est en conflit avec au moins un filtre de Type de modification, parce que certains types de modification ne peuvent être marqués comme « mineurs ». Les filtres en conflit sont marqués dans la zone Filtres actifs ci-dessus.",
-       "rcfilters-hideminor-conflicts-typeofchange": "Certains types de modification ne peuvent pas être qualifiés de « mineurs », donc ce filtre est en conflit avec les filtres de Type de modification suivants : $1",
-       "rcfilters-typeofchange-conflicts-hideminor": "Ce filtre de Type de modification est en conflit avec le filtre « Modifications mineures ». Certains type sde modification ne peuvent pas être indiqués comme « mineurs ».",
+       "rcfilters-hideminor-conflicts-typeofchange-global": "Le filtre « Modifications mineures » est en conflit avec au moins un filtre de Type de modification, parce que certains types de modification ne peuvent être marqués comme « mineurs ». Les filtres en conflit sont marqués dans la zone Filtres actifs ci-dessus.",
+       "rcfilters-hideminor-conflicts-typeofchange": "Certains types de modification ne peuvent pas être qualifiés de « mineurs », donc ce filtre est en conflit avec les filtres de Type de modification suivants : $1",
+       "rcfilters-typeofchange-conflicts-hideminor": "Ce filtre de Type de modification est en conflit avec le filtre « Modifications mineures ». Certains type sde modification ne peuvent pas être indiqués comme « mineurs ».",
        "rcfilters-filtergroup-lastrevision": "Dernières révisions",
        "rcfilters-filter-lastrevision-label": "Dernière révision",
        "rcfilters-filter-lastrevision-description": "Uniquement la dernière modification apportée à une page.",
        "rcfilters-filter-previousrevision-label": "Pas la dernière version",
-       "rcfilters-filter-previousrevision-description": "Toutes les modifications apportées à une page et qui ne concernent pas la « dernière version ».",
+       "rcfilters-filter-previousrevision-description": "Toutes les modifications apportées à une page et qui ne concernent pas la « dernière version ».",
        "rcfilters-filter-excluded": "Exclu",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
        "rcfilters-exclude-button-off": "Exclure les sélectionnés",
        "uploadscripted": "Ce fichier contient du code HTML ou un script qui pourrait être interprété de façon incorrecte par un navigateur web.",
        "upload-scripted-pi-callback": "Impossible de charger un fichier qui contient des instructions de traitement de feuille de style XML.",
        "upload-scripted-dtd": "Impossible de téléverser des fichiers SVG qui contiennent une déclaration de DTD non standard.",
-       "uploaded-script-svg": "Élément scriptable « $1 » trouvé dans le fichier SVG téléversé.",
+       "uploaded-script-svg": "Élément scriptable « $1 » trouvé dans le fichier SVG téléversé.",
        "uploaded-hostile-svg": "CSS non sûr trouvé dans l’élément style d’un fichier SVG téléversé.",
        "uploaded-event-handler-on-svg": "Fixer des attributs de gestionnaire d’événement <code>$1=\"$2\"</code> n’est pas autorisé dans les fichiers SVG.",
        "uploaded-href-attribute-svg": "<a> les éléments ne peuvent être liés (href) qu’aux cibles de données : (fichier inclus), http:// ou https://, ou un fragment (#, même document). Pour les autres éléments, comme <image>, seuls data: et fragment sont autorisés. Essayez les images incluses lors de l’export de votre SVG. <code>&lt;$1 $2=\"$3\"&gt;</code> obtenu.",
        "uploaded-href-unsafe-target-svg": "Un href vers des données non sûres a été trouvé dans le fichier SVG téléversé : URI cible <code>&lt;$1 $2=\"$3\"&gt;</code>.",
-       "uploaded-animate-svg": "Balise « animate » trouvée, qui pourrait modifier le href en utilisant l’attribut « from » <code>&lt;$1 $2=\"$3\"&gt;</code> dans le fichier SVG téléversé.",
+       "uploaded-animate-svg": "Balise « animate » trouvée qui pourrait modifier le href en utilisant l’attribut « from » <code>&lt;$1 $2=\"$3\"&gt;</code> dans le fichier SVG téléversé.",
        "uploaded-setting-event-handler-svg": "Positionner les attributs du gestionnaire d’événements n'est pas possible, <code>&lt;$1 $2=\"$3\"&gt;</code> trouvé dans le fichier SVG téléversé.",
-       "uploaded-setting-href-svg": "L’utilisation de la balise « set » pour ajouter un attribut « href » à l’élément parent est interdite.",
-       "uploaded-wrong-setting-svg": "L’utilisation de la balise « set » pour ajouter une cible distante, de données ou de type script,  à un attribut quelconque, est interdite. <code>&lt;set to=\"$1\"&gt;</code> a été trouvé dans le fichier SVG téléversé.",
-       "uploaded-setting-handler-svg": "Les SVG qui positionnent l’attribut « handler » avec distant/données/script sont bloqués. <code>$1=\"$2\"</code> a été trouvé dans le fichier SVG téléversé.",
+       "uploaded-setting-href-svg": "L’utilisation de la balise « set » pour ajouter un attribut « href » à l’élément parent est interdite.",
+       "uploaded-wrong-setting-svg": "L’utilisation de la balise « set » pour ajouter à un attribut quelconque une cible distante, de données ou de script est interdite. <code>&lt;set to=\"$1\"&gt;</code> a été trouvé dans le fichier SVG téléversé.",
+       "uploaded-setting-handler-svg": "Les SVG qui positionnent l’attribut « handler » avec distant/données/script sont bloqués. <code>$1=\"$2\"</code> a été trouvé dans le fichier SVG téléversé.",
        "uploaded-remote-url-svg": "Les SVG qui positionnent un attribut de style avec une URL distante sont bloqués. <code>$1=\"$2\"</code> trouvé dans le fichier SVG téléversé.",
        "uploaded-image-filter-svg": "Filtre d’image avec URL trouvé : <code>&lt;$1 $2=\"$3\"&gt;</code> dans le fichier SVG téléversé.",
        "uploadscriptednamespace": "Ce fichier SVG contient un espace de noms '<nowiki>$1</nowiki>' non autorisé.",
        "backend-fail-read": "Impossible de lire le fichier \"$1\".",
        "backend-fail-create": "Impossible d’écrire le fichier « $1 ».",
        "backend-fail-maxsize": "Impossible d’écrire le fichier « $1 » parce qu’il est plus grand {{PLURAL:$2|qu’un octet|que $2 octets}}.",
-       "backend-fail-readonly": "Le support de stockage « $1 » est actuellement en lecture seule. La raison indiquée est : <em>$2</em>",
+       "backend-fail-readonly": "Le support de stockage « $1 » est actuellement en lecture seule. La raison indiquée est : <em>$2</em>",
        "backend-fail-synced": "Le fichier « $1 » est dans un état incohérent dans les supports de stockage internes",
        "backend-fail-connect": "Impossible de se connecter au média de stockage « $1 ».",
        "backend-fail-internal": "Une erreur inconnue s’est produite dans le média de stockage « $1 ».",
        "backend-fail-contenttype": "Impossible de déterminer le type de contenu du fichier à stocker en « $1 ».",
        "backend-fail-batchsize": "On a fourni au support de stockage un lot de $1 {{PLURAL:$1|opération|opérations}} de fichier; la limite est $2 {{PLURAL:$2|opération|opérations}}.",
        "backend-fail-usable": "Impossible de lire ou d’écrire le fichier « $1 » en raison de droits insuffisants ou de répertoires/conteneurs manquants.",
-       "backend-fail-stat": "Impossible de lire l’état du fichier « $1 ».",
-       "backend-fail-hash": "Impossible de déterminer le hachage cryptographique du fichier « $1 ».",
+       "backend-fail-stat": "Impossible de lire l’état du fichier « $1 ».",
+       "backend-fail-hash": "Impossible de déterminer le hachage cryptographique du fichier « $1 ».",
        "filejournal-fail-dbconnect": "Impossible de se connecter à la base de données du journal pour le terminal de stockage « $1 ».",
        "filejournal-fail-dbquery": "Impossible de mettre à jour la base de données du journal pour le terminal de stockage « $1 ».",
        "lockmanager-notlocked": "Impossible de déverrouiller « $1 » ; elle n'est pas verrouillée.",
        "lockmanager-fail-closelock": "Impossible de fermer le fichier de verrou pour « $1 ».",
        "lockmanager-fail-deletelock": "Impossible de supprimer le fichier de verrou pour « $1 ».",
        "lockmanager-fail-acquirelock": "Impossible d'obtenir le verrou pour « $1 ».",
-       "lockmanager-fail-openlock": "Impossible d’ouvrir le fichier de verrou pour « $1 » . Assurez-vous que votre répertoire de téléchargement est configuré correctement et que votre serveur web a le droit d’y écrire. Voyez https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory pour plus d’information.",
+       "lockmanager-fail-openlock": "Impossible d’ouvrir le fichier de verrou pour « $1 » . Assurez-vous que votre répertoire de téléchargement est configuré correctement et que votre serveur web a le droit d’y écrire. Voyez https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory pour plus d’information.",
        "lockmanager-fail-releaselock": "Impossible de relâcher le verrou pour « $1 ».",
        "lockmanager-fail-db-bucket": "Impossible de contacter suffisamment de bases de données de verrouillage dans le godet $1.",
        "lockmanager-fail-db-release": "Impossible de relâcher les verrous sur la base de données $1.",
        "uploadstash-errclear": "La suppression des fichiers a échoué.",
        "uploadstash-refresh": "Actualiser la liste des fichiers",
        "uploadstash-thumbnail": "afficher la vignette",
-       "uploadstash-exception": "Impossible de stocker le téléversement dans la réserve ($1) : « $2 ».",
+       "uploadstash-exception": "Impossible de stocker le téléversement dans la réserve ($1) : « $2 ».",
        "uploadstash-bad-path": "Le chemin n’existe pas.",
        "uploadstash-bad-path-invalid": "Le chemin n’est pas valide.",
-       "uploadstash-bad-path-unknown-type": "Type « $1 » inconnu.",
+       "uploadstash-bad-path-unknown-type": "Type « $1 » inconnu.",
        "uploadstash-bad-path-unrecognized-thumb-name": "Nom de vignette non reconnu.",
        "uploadstash-bad-path-no-handler": "Aucun gestionnaire trouvé pour le type MIME $1 du fichier $2.",
-       "uploadstash-bad-path-bad-format": "La clé « $1 » n’est pas dans un format correct.",
-       "uploadstash-file-not-found": "La clé « $1 » n’a pas été trouvée dans la réserve.",
+       "uploadstash-bad-path-bad-format": "La clé « $1 » n’est pas dans un format correct.",
+       "uploadstash-file-not-found": "La clé « $1 » n’a pas été trouvée dans la réserve.",
        "uploadstash-file-not-found-no-thumb": "Impossible d’obtenir une vignette.",
        "uploadstash-file-not-found-no-local-path": "Aucun chemin local pour l’élément mis à l’échelle.",
        "uploadstash-file-not-found-no-object": "Impossible de créer l’objet fichier local pour la vignette.",
        "listfiles-delete": "supprimer",
        "listfiles-summary": "Cette page spéciale permet de lister tous les fichiers importés.",
        "listfiles_search_for": "Rechercher un nom de média :",
-       "listfiles-userdoesnotexist": "Le compte utilisateur « $1 » n’est pas enregistré.",
+       "listfiles-userdoesnotexist": "Le compte utilisateur « $1 » n’est pas enregistré.",
        "imgfile": "fichier",
        "listfiles": "Liste de fichiers",
        "listfiles_thumb": "Miniature",
        "randompage": "Page au hasard",
        "randompage-nopages": "Il n'y a aucune page dans {{PLURAL:$2|l'espace de noms|les espaces de noms}} : $1.",
        "randomincategory": "Page au hasard dans la catégorie",
-       "randomincategory-invalidcategory": "« $1 » n’est pas un nom de catégorie valide.",
+       "randomincategory-invalidcategory": "« $1 » n’est pas un nom de catégorie valide.",
        "randomincategory-nopages": "Il n’y a pas de pages dans la catégorie [[:Category:$1|$1]].",
        "randomincategory-category": "Catégorie :",
        "randomincategory-legend": "Page aléatoire dans la catégorie",
        "ancientpages": "Pages les plus anciennement modifiées",
        "move": "Renommer",
        "movethispage": "Renommer cette page",
-       "unusedimagestext": "Les fichiers suivants existent, mais ne sont inclus dans aucune page.\nVeuillez noter que d’autres sites peuvent accéder à ces fichiers à l’aide de liens directs (URLs), et donc qu’un fichier peut être listé ici alors qu’il est utilisé par ces sites.",
+       "unusedimagestext": "Les fichiers suivants existent, mais ne sont inclus dans aucune page.\nVeuillez noter que d’autres sites peuvent accéder à ces fichiers à l’aide de liens directs (URL), et donc qu’un fichier peut être listé ici alors qu’il est utilisé par ces sites.",
        "unusedimagestext-categorizedimgisused": "Les fichiers suivants existent mais ne sont inclus dans aucune page. Les images catégorisées sont considérées comme utilisées malgré qu'elles ne soient pas incluses dans aucune page.\nVeuillez noter que les autres sites web peuvent créer un lien vers un fichier à l'aide d'une URL directe, et donc peuvent encore être listés ici malgré qu'ils soient encore utilisés.",
        "unusedcategoriestext": "Les pages de catégories suivantes existent, mais ne sont utilisées par aucune autre page ni catégorie.",
        "notargettitle": "Pas de cible",
        "suppress": "Supprimer",
        "querypage-disabled": "Cette page spéciale est désactivée pour des raisons de performances.",
        "apihelp": "Aide de l’API",
-       "apihelp-no-such-module": "Le module « $1 » est introuvable.",
+       "apihelp-no-such-module": "Le module « $1 » est introuvable.",
        "apisandbox": "Bac à sable de l'API",
        "apisandbox-jsonly": "Le bac à sable de l'API nécessite JavaScript",
        "apisandbox-intro": "Utilisez cette page pour expérimenter l’<strong>API webservice de MediaWiki</strong>.\nReportez-vous à [[mw:API:Main page|la documentation de l’API]] pour plus de détails sur l’utilisation de l’API. Exemple: [https://www.mediawiki.org/wiki/API#A_simple_example obtenir le contenu d'une page principale]. Choisissez une option pour voir d'autres exemples.",
        "emailccsubject": "Copie de votre message à $1 : $2",
        "emailsent": "Courriel envoyé",
        "emailsenttext": "Votre message a été envoyé par courriel.",
-       "emailuserfooter": "Ce courriel a été {{GENDER:$1|envoyé}} par « $1 » à « {{GENDER:$2|$2}} » par la fonction « {{int:emailuser}} » de {{SITENAME}}. Si {{GENDER:$2|vous}} répondez à ce courriel, {{GENDER:$2|votre}} courriel sera envoyé directement à l’{{GENDER:$1|émetteur initial}}, en {{GENDER:$1|lui}} mentionnant {{GENDER:$2|votre}} adresse courriel .",
+       "emailuserfooter": "Ce courriel a été {{GENDER:$1|envoyé}} par « $1 » à « {{GENDER:$2|$2}} » par la fonction « {{int:emailuser}} » de {{SITENAME}}. Si {{GENDER:$2|vous}} répondez à ce courriel, {{GENDER:$2|votre}} courriel sera envoyé directement à l’{{GENDER:$1|émetteur initial}}, en {{GENDER:$1|lui}} mentionnant {{GENDER:$2|votre}} adresse courriel .",
        "usermessage-summary": "Laisser un message système.",
        "usermessage-editor": "Messager du système",
        "watchlist": "Liste de suivi",
        "watchnologin": "Non connecté",
        "addwatch": "Ajouter à la liste de suivi",
        "addedwatchtext": "La page « [[:$1]] » et sa page de discussion ont été ajoutées à votre [[Special:Watchlist|liste de suivi]].",
-       "addedwatchtext-talk": "« [[:$1]] » et sa page associée ont été ajoutés à votre [[Special:Watchlist|liste de suivi]].",
-       "addedwatchtext-short": "La page « $1 » a été ajoutée à votre liste de suivi.",
+       "addedwatchtext-talk": "« [[:$1]] » et sa page associée ont été ajoutés à votre [[Special:Watchlist|liste de suivi]].",
+       "addedwatchtext-short": "La page « $1 » a été ajoutée à votre liste de suivi.",
        "removewatch": "Supprimer de la liste de suivi",
-       "removedwatchtext": "La page « [[:$1]] » et sa page de discussion ont été retirées de votre [[Special:Watchlist|liste de suivi]].",
-       "removedwatchtext-talk": "« [[:$1]] » et sa page associée ont été supprimés de votre [[Special:Watchlist|liste de suivi]].",
-       "removedwatchtext-short": "La page « $1 » a été supprimée de votre liste de suivi.",
+       "removedwatchtext": "La page « [[:$1]] » et sa page de discussion ont été retirées de votre [[Special:Watchlist|liste de suivi]].",
+       "removedwatchtext-talk": "« [[:$1]] » et sa page associée ont été supprimés de votre [[Special:Watchlist|liste de suivi]].",
+       "removedwatchtext-short": "La page « $1 » a été supprimée de votre liste de suivi.",
        "watch": "Suivre",
        "watchthispage": "Suivre cette page",
        "unwatch": "Ne plus suivre",
        "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>.",
        "revertpage": "Révocation des modifications de [[Special:Contributions/$2|$2]] ([[User talk:$2|discussion]]) vers la dernière version de [[User:$1|$1]]",
+       "revertpage-anon": "Modifications de [[Special:Contributions/$2|$2]] révoquées vers la dernière version de [[User:$1|$1]]",
        "revertpage-nouser": "Révocation des modifications par un utilisateur masqué à la dernière version par {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Révocation des modifications effectuées par {{GENDER:$3|$1}} ;\nrétablissement de la dernière version par {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Erreur de session",
        "sessionfailure": "Votre session de connexion semble avoir des problèmes ;\ncette action a été annulée en prévention d'un piratage de session.\nVeuillez soumettre le formulaire de nouveau.",
        "changecontentmodel": "Modifier le modèle de contenu d’une page",
        "changecontentmodel-legend": "Modifier le modèle de contenu",
-       "changecontentmodel-title-label": "Titre de la page :",
+       "changecontentmodel-title-label": "Titre de la page:",
        "changecontentmodel-current-label": "Modèle de contenu actuel :",
-       "changecontentmodel-model-label": "Nouveau modèle de contenu :",
+       "changecontentmodel-model-label": "Nouveau modèle de contenu:",
        "changecontentmodel-reason-label": "Motif :",
        "changecontentmodel-submit": "Modifier",
        "changecontentmodel-success-title": "Le modèle de contenu a été modifié",
        "changecontentmodel-emptymodels-text": "Le contenu sur [[:$1]] ne peut être converti en aucun type.",
        "log-name-contentmodel": "Journal de modification de modèle de contenu",
        "log-description-contentmodel": "Cette page montre des modifications dans le modèle de contenu des pages, ainsi que les pages créées avec un modèle de contenu différent du contenu par défaut.",
-       "logentry-contentmodel-new": "$1 {{GENDER:$2|a créé}} la page $3 en utilisant un modèle de contenu « $5 » autre que celui par défaut",
-       "logentry-contentmodel-change": "$1 {{GENDER:$2|a modifié}} le modèle de contenu de la page $3 de « $4 » en « $5 »",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|a créé}} la page $3 en utilisant un modèle de contenu « $5 » autre que celui par défaut",
+       "logentry-contentmodel-change": "$1 {{GENDER:$2|a modifié}} le modèle de contenu de la page $3 de « $4 » en « $5 »",
        "logentry-contentmodel-change-revertlink": "rétablir",
        "logentry-contentmodel-change-revert": "rétablir",
        "protectlogpage": "Journal des protections",
        "unprotectedarticle": "a supprimé la protection de « [[$1]] »",
        "movedarticleprotection": "a déplacé les paramètres de protection depuis « [[$2]] » vers « [[$1]] »",
        "protectedarticle-comment": "{{GENDER:$2|A protégé}} « [[$1]] »",
-       "modifiedarticleprotection-comment": "{{GENDER:$2|A changé le niveau de protection}} pour « [[$1]] »",
-       "unprotectedarticle-comment": "{{GENDER:$2|A supprimé la protection}} de « [[$1]] »",
+       "modifiedarticleprotection-comment": "{{GENDER:$2|A changé le niveau de protection}} pour « [[$1]] »",
+       "unprotectedarticle-comment": "{{GENDER:$2|A supprimé la protection}} de « [[$1]] »",
        "protect-title": "Changer le niveau de protection pour « $1 »",
        "protect-title-notallowed": "Voir le niveau de protection de « $1 »",
        "prot_1movedto2": "[[$1]] renommé en [[$2]]",
        "anoncontribs": "Contributions",
        "contribsub2": "Pour {{GENDER:$3|$1}} ($2)",
        "contributions-subtitle": "Pour {{GENDER:$3|$1}}",
-       "contributions-userdoesnotexist": "Le compte d’utilisateur « $1 » n’est pas enregistré.",
+       "contributions-userdoesnotexist": "Le compte d’utilisateur « $1 » n’est pas enregistré.",
        "negative-namespace-not-supported": "Les espaces de noms avec des valeurs négatives ne sont pas supportés.",
        "nocontribs": "Aucune modification correspondant à ces critères n'a été trouvée.",
        "uctop": "actuelle",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] a été bloqué{{GENDER:$1||e}}.<br />\nConsultez la [[Special:BlockList|liste des blocages]] pour voir les utilisateurs bloqués.",
        "ipb-blockingself": "Vous êtes sur le point de bloquer votre propre compte ! Êtes-vous certain{{GENDER:||e}} de vouloir faire cela ?",
        "ipb-confirmhideuser": "Vous êtes sur le point de bloquer un utilisateur avec « cacher l'utilisateur » activé. Cela supprimera le nom de l'utilisateur dans toutes les listes et les entrées du journal. Êtes-vous sûr{{GENDER:||e}} de vouloir le faire ?",
-       "ipb-confirmaction": "Si vous êtes sûr{{GENDER:||e}} de vraiment vouloir le faire, veuillez cocher le champ « {{int:ipb-confirm}} » en bas.",
+       "ipb-confirmaction": "Si vous êtes sûr{{GENDER:||e}} de vraiment vouloir le faire, veuillez cocher le champ « {{int:ipb-confirm}} » en bas.",
        "ipb-edit-dropdown": "Modifier les motifs de blocage par défaut",
        "ipb-unblock-addr": "Débloquer $1",
        "ipb-unblock": "Débloquer un compte utilisateur ou une adresse IP",
        "empty-username": "(aucun nom d’utilisateur disponible)",
        "contribslink": "contributions",
        "emaillink": "envoyer un courriel",
-       "autoblocker": "Vous avez été bloqué automatiquement parce que votre adresse IP a été récemment utilisée par « [[User:$1|$1]] ».\nLe motif fourni pour le blocage de $1 est « $2 »",
+       "autoblocker": "Vous avez été bloqué automatiquement parce que votre adresse IP a été récemment utilisée par « [[User:$1|$1]] ».\nLe motif fourni pour le blocage de $1 est « $2 »",
        "blocklogpage": "Journal des blocages",
        "blocklog-showlog": "Cet utilisateur a été bloqué précédemment. \nLe journal des blocages est affiché ci-dessous pour référence :",
        "blocklog-showsuppresslog": "Cet utilisateur a été bloqué et masqué précédemment. \nLe journal des masquages est affiché ci-dessous pour référence :",
        "cant-move-category-page": "Vous n'avez pas la permission de renommer les pages de catégorie.",
        "cant-move-to-category-page": "Vous n'avez pas la permission de renommer une page vers une page de catégorie.",
        "cant-move-subpages": "Vous n’avez pas le droit de renommer des sous-pages.",
-       "namespace-nosubpages": "L’espace de noms « $1 » n’autorise pas les sous-pages.",
+       "namespace-nosubpages": "L’espace de noms « $1 » n’autorise pas les sous-pages.",
        "newtitle": "Nouveau titre :",
        "move-watch": "Suivre les pages originale et nouvelle",
        "movepagebtn": "Renommer la page",
        "movenosubpage": "Cette page n'a aucune sous-page.",
        "movereason": "Motif :",
        "revertmove": "rétablir",
-       "delete_and_move_text": "La page de destination « [[:$1]] » existe déjà.\nÊtes-vous certain{{GENDER:||e|}} de vouloir la supprimer pour permettre ce renommage ?",
+       "delete_and_move_text": "La page de destination « [[:$1]] » existe déjà.\nÊtes-vous certain{{GENDER:||e|}} de vouloir la supprimer pour permettre ce renommage ?",
        "delete_and_move_confirm": "Oui, supprimer la page de destination",
        "delete_and_move_reason": "Page supprimée pour permettre le renommage depuis « [[$1]] »",
        "selfmove": "Le titre est le même ;\nimpossible de renommer une page sur elle-même.",
        "immobile-source-namespace": "Vous ne pouvez pas renommer les pages dans l'espace de noms « $1 »",
-       "immobile-source-namespace-iw": "Il n'est pas possible de déplacer les pages depuis ce wiki vers les autres wikis.",
+       "immobile-source-namespace-iw": "Il nest pas possible de déplacer les pages depuis ce wiki vers les autres wikis.",
        "immobile-target-namespace": "Vous ne pouvez pas renommer des pages vers l’espace de noms « $1 ».",
        "immobile-target-namespace-iw": "Un lien interwiki n’est pas une cible valide pour un renommage de page.",
        "immobile-source-page": "Cette page n'est pas renommable.",
        "import-upload": "Import de données XML",
        "import-token-mismatch": "Perte des données de session.\n\nVous avez peut-être été déconnecté. '''Veuillez vérifier que vous êtes toujours connecté et réessayez'''.\nSi cela ne fonctionne toujours pas, essayez de [[Special:UserLogout|vous déconnecter]] et de vous reconnecter, et vérifiez que votre navigateur accepte les témoins (''cookies'') de ce site.",
        "import-invalid-interwiki": "Impossible d'importer depuis le wiki spécifié.",
-       "import-error-edit": "La page « $1 » n’a pas été importée parce que vous n’êtes pas autorisé à la modifier.",
-       "import-error-create": "La page « $1 » n’a pas été importée parce que vous n’êtes pas autorisé à la créer.",
-       "import-error-interwiki": "La page « $1 » n’a pas été importée parce que son nom est réservé pour un lien externe (interwiki).",
-       "import-error-special": "La page « $1 » n’a pas été importée parce qu’elle appartient à un espace de noms spécial qui n’autorise aucune page.",
-       "import-error-invalid": "Page « $1 » n’a pas été importée parce que le nom sous lequel elle aurait été importée n’est pas valide sur ce wiki.",
+       "import-error-edit": "La page « $1 » n’a pas été importée parce que vous n’êtes pas autorisé à la modifier.",
+       "import-error-create": "La page « $1 » n’a pas été importée parce que vous n’êtes pas autorisé à la créer.",
+       "import-error-interwiki": "La page « $1 » n’a pas été importée parce que son nom est réservé pour un lien externe (interwiki).",
+       "import-error-special": "La page « $1 » n’a pas été importée parce qu’elle appartient à un espace de noms spécial qui n’autorise aucune page.",
+       "import-error-invalid": "Page « $1 » n’a pas été importée parce que le nom sous lequel elle aurait été importée n’est pas valide sur ce wiki.",
        "import-error-unserialize": "La révision $2 de la page « $1 » ne peut pas être désérialisée. La révision est indiquée comme utilisant le modèle de contenu $3 sérialisé en $4.",
-       "import-error-bad-location": "La révision $2 utilisant le modèle de contenu $3 n’a pas pu être stockée sur « $1 » sur ce wiki, car ce modèle n’est pas pris en charge sur cette page.",
+       "import-error-bad-location": "La révision $2 utilisant le modèle de contenu $3 n’a pas pu être stockée sur « $1 » sur ce wiki, car ce modèle n’est pas pris en charge sur cette page.",
        "import-options-wrong": "{{PLURAL:$2|Mauvaise option|Mauvaises options}} : <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "La page racine fournie est un titre non valide.",
        "import-rootpage-nosubpage": "L'espace de noms « $1 » de la page racine n'autorise pas les sous-pages.",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|révision importée|révisions importées}}",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|révision importée|révisions importées}} depuis $2",
        "javascripttest": "Test de JavaScript",
-       "javascripttest-pagetext-unknownaction": "Action « $1 » inconnue.",
+       "javascripttest-pagetext-unknownaction": "Action « $1 » inconnue.",
        "javascripttest-qunit-intro": "Voir [$1 la documentation de test] sur mediawiki.org.",
        "tooltip-pt-userpage": "Votre page d’{{GENDER:|utilisateur|utilisatrice}}",
        "tooltip-pt-anonuserpage": "La page utilisateur avec l'adresse IP de laquelle vous contribuez",
        "confirmemail_subject": "Confirmation d’adresse de courriel pour {{SITENAME}}",
        "confirmemail_body": "Quelqu’un, probablement vous, à partir de l’adresse IP $1,\na créé un compte « $2 » avec cette adresse de courriel sur le site {{SITENAME}}.\n\nPour confirmer que ce compte vous appartient vraiment et afin\nd’activer les fonctions de messagerie sur {{SITENAME}},\nveuillez suivre ce lien dans votre navigateur :\n\n$3\n\nSi vous n’avez *pas* créé ce compte, suivez le lien ci-dessous \npour annuler la confirmation de votre adresse courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
        "confirmemail_body_changed": "Quelqu’un, probablement vous, à partir de l’adresse IP $1,\na modifié l’adresse de courriel associée au compte « $2 » de {{SITENAME}}\nen cette adresse.\n\nPour confirmer que ce compte vous appartient vraiment et afin\nde réactiver les fonctions de messagerie sur {{SITENAME}},\nveuillez suivre ce lien dans votre navigateur :\n\n$3\n\nSi ce compte ne vous appartient *pas*, n’ouvrez pas ce lien ;\nvous pouvez suivre l’autre lien ci-dessous pour annuler la\nconfirmation de votre adresse courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
-       "confirmemail_body_set": "Quelqu’un, probablement vous, depuis l’adresse IP $1, a modifié l’adresse de courriel du compte « $2 » en celle-ci sur {{SITENAME}}.\n\nPour confirmer que ce compte vous appartient et réactiver les fonctions de courriel sur {{SITENAME}}, ouvrez ce lien dans votre navigateur Web :\n\n$3\n\nSi le compte ne vous appartient *pas*, suivez plutôt ce lien pour annuler la confirmation de l’adresse de courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
+       "confirmemail_body_set": "Quelqu’un, probablement vous, depuis l’adresse IP $1, a modifié l’adresse de courriel du compte « $2 » en celle-ci sur {{SITENAME}}.\n\nPour confirmer que ce compte vous appartient et réactiver les fonctions de courriel sur {{SITENAME}}, ouvrez ce lien dans votre navigateur Web :\n\n$3\n\nSi le compte ne vous appartient *pas*, suivez plutôt ce lien pour annuler la confirmation de l’adresse de courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
        "confirmemail_invalidated": "Confirmation de l’adresse courriel annulée",
        "invalidateemail": "Annuler la confirmation de l'adresse de courriel",
        "notificationemail_subject_changed": "L’adresse courriel enregistrée sur {{SITENAME}} a été changée",
        "parentheses-start": "(",
        "parentheses-end": ")",
        "brackets": "[$1]",
-       "quotation-marks": "« $1 »",
+       "quotation-marks": "« $1 »",
        "imgmultipageprev": "← page précédente",
        "imgmultipagenext": "page suivante →",
        "imgmultigo": "Accéder !",
        "tags-deactivate-submit": "Désactiver",
        "tags-apply-no-permission": "Vous n’avez pas le droit d’appliquer des balises de changement en même temps que vos modifications.",
        "tags-apply-blocked": "Vous ne pouvez pas appliquer les modifications de balises et vos modifications lorsque vous êtes bloqué{{GENDER:$1||e}}.",
-       "tags-apply-not-allowed-one": "La balise « $1 » n’est pas autorisée à être appliquée manuellement.",
+       "tags-apply-not-allowed-one": "La balise « $1 » n’est pas autorisée à être appliquée manuellement.",
        "tags-apply-not-allowed-multi": "{{PLURAL:$2|La balise suivante n’est pas autorisée à être appliquée|Les balises suivantes ne sont pas autorisées à être appliquées}} manuellement : $1",
        "tags-update-no-permission": "Vous n’avez pas le droit d’ajouter ou de supprimer \nles balises de modification des révisions individuelles ou des entrées de journal.",
        "tags-update-blocked": "Vous ne pouvez pas ajouter ou supprimer des balises de modifications lorsque vous êtes bloqué{{GENDER:$1||e}}.",
-       "tags-update-add-not-allowed-one": "La balise « $1 » ne peut pas être ajoutée manuellement.",
+       "tags-update-add-not-allowed-one": "La balise « $1 » ne peut pas être ajoutée manuellement.",
        "tags-update-add-not-allowed-multi": "{{PLURAL:$2|La balise suivante ne peut pas être ajoutée|Les balises suivantes ne peuvent pas être ajoutées}} manuellement : $1",
-       "tags-update-remove-not-allowed-one": "La balise « $1 » ne peut pas être enlevée.",
+       "tags-update-remove-not-allowed-one": "La balise « $1 » ne peut pas être enlevée.",
        "tags-update-remove-not-allowed-multi": "{{PLURAL:$2|La balise suivante ne peut pas être enlevée|Les balises suivantes ne peuvent pas être enlevées}} manuellement : $1",
        "tags-edit-title": "Modifier les balises",
        "tags-edit-manage-link": "Gérer les balises",
        "logentry-upload-revert": "$1 {{GENDER:$2|a annulé}} $3 vers une ancienne version",
        "log-name-managetags": "Journal des modifications de balises",
        "log-description-managetags": "Cette page recense les tâches de maintenance liées aux [[Special:Tags|balises]]. Le journal contient uniquement les actions faites manuellement par un administrateur ; les balises peuvent être créées ou supprimées par le logiciel wiki sans que cette action ne soit inscrite dans ce journal.",
-       "logentry-managetags-create": "$1 {{GENDER:$2|a créé}} la balise « $4 ».",
-       "logentry-managetags-delete": "$1 {{GENDER:$2|a supprimé}} la balise « $4 » (retirée {{PLURAL:$5|d'une révision ou entrée de journal|de $5 révisions ou entrées de journal}})",
-       "logentry-managetags-activate": "$1 {{GENDER:$2|a activé}} la balise « $4 » pour l’usage des utilisateurs et des robots",
-       "logentry-managetags-deactivate": "$1 {{GENDER:$2|a désactivé}} la balise « $4 » pour l’usage des utilisateurs et des robots",
+       "logentry-managetags-create": "$1 {{GENDER:$2|a créé}} la balise « $4 ».",
+       "logentry-managetags-delete": "$1 {{GENDER:$2|a supprimé}} la balise « $4 » (retirée {{PLURAL:$5|d'une révision ou entrée de journal|de $5 révisions ou entrées de journal}})",
+       "logentry-managetags-activate": "$1 {{GENDER:$2|a activé}} la balise « $4 » pour l’usage des utilisateurs et des robots",
+       "logentry-managetags-deactivate": "$1 {{GENDER:$2|a désactivé}} la balise « $4 » pour l’usage des utilisateurs et des robots",
        "log-name-tag": "Journal des balises",
        "log-description-tag": "Cette page montre quand des utilisateurs ont ajouté ou supprimé des [[Special:Tags|balises]] de révisions individuelles ou d’entrées de journal. Le journal ne liste pas les actions de marquage quand elles ont lieu au cours d’une modification, d’une suppression, ou d’une action semblable.",
        "logentry-tag-update-add-revision": "$1 {{GENDER:$2|a ajouté}} {{PLURAL:$7|la balise|les balises}} $6 à la révision $4 de la page $3",
        "api-error-emptypage": "Création de pages vide n'est pas autorisée.",
        "api-error-publishfailed": "Erreur interne : Le serveur n'a pas pu publier le fichier temporaire.",
        "api-error-stashfailed": "Erreur interne : le serveur n'a pas pu enregistrer le fichier temporaire.",
-       "api-error-unknown-warning": "Avertissement inconnu : « $1 ».",
+       "api-error-unknown-warning": "Avertissement inconnu : « $1 ».",
        "api-error-unknownerror": "Erreur inconnue : « $1 ».",
        "duration-seconds": "$1 seconde{{PLURAL:$1||s}}",
        "duration-minutes": "$1 minute{{PLURAL:$1||s}}",
        "authmanager-link-not-in-progress": "La liaison de compte n’est pas en cours ou les données de session ont été perdues. Veuillez recommencer depuis le début.",
        "authmanager-autocreate-noperm": "La création automatique de compte n’est pas autorisée.",
        "authmanager-autocreate-exception": "La création automatique de compte est désactivée temporairement, du fait d’erreurs antérieures.",
-       "authmanager-userdoesnotexist": "Le compte utilisateur « $1 » n’est pas inscrit.",
+       "authmanager-userdoesnotexist": "Le compte utilisateur « $1 » n’est pas inscrit.",
        "authmanager-userlogin-remembermypassword-help": "Indique si le mot de passe doit être mémorisé au-delà de la durée de la session.",
        "authmanager-username-help": "Nom d’utilisateur pour l’authentification.",
        "authmanager-password-help": "Mot de passe pour l’authentification.",
        "authprovider-confirmlink-ok-help": "Continuer après l’affichage des messages d’échec de liaison.",
        "authprovider-resetpass-skip-label": "Sauter",
        "authprovider-resetpass-skip-help": "Sauter la réinitialisation du mot de passe.",
-       "authform-nosession-login": "L’authentification a réussi, mais votre navigateur ne peut pas se « souvenir » d’avoir été connecté.\n\n$1",
-       "authform-nosession-signup": "Le compte a été créé, mais votre navigateur ne peut pas se « souvenir » avoir été connecté.\n\n$1",
+       "authform-nosession-login": "L’authentification a réussi, mais votre navigateur ne peut pas se « souvenir » d’avoir été connecté.\n\n$1",
+       "authform-nosession-signup": "Le compte a été créé, mais votre navigateur ne peut pas se « souvenir » avoir été connecté.\n\n$1",
        "authform-newtoken": "Jeton manquant. $1",
        "authform-notoken": "Jeton manquant",
        "authform-wrongtoken": "Mauvais jeton",
index b4deae8..d8dffa5 100644 (file)
@@ -28,7 +28,8 @@
                        "Athena in Wonderland",
                        "Navhy",
                        "PokéDex Nacional",
-                       "Maria zaos"
+                       "Maria zaos",
+                       "Iváns"
                ]
        },
        "tog-underline": "Subliñar as ligazóns:",
index 853ef7b..2a427a2 100644 (file)
        "category-file-count": "{{PLURAL:$2|ह्या वर्गांत फकत सकयली फायल आसपावता.|ह्या वर्गांत सकयल दिल्लीं {{PLURAL:$1|फायल|$1 फायलीं}} आसता, वट्ट फायलीं $2}}",
        "listingcontinuesabbrev": "चालू.",
        "noindex-category": "बिननिर्देशांकी पानां",
+       "broken-file-category": "तुटलेल्या फायलींचो दुवे आसलेलीं पानां",
        "about": "विशीं",
        "article": "मजकूराचीं पानां",
        "newwindow": "(नव्या ज़ोणेलांत उकतें जाता)",
        "redirectedfrom": "($1 सून पुनर्निर्देशित)",
        "redirectpagesub": "पान परतून निर्देशीत करचें",
        "redirectto": "हांगां पुनर्निर्देशित:",
-       "lastmodifiedat": "हà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\82त à¤¨à¤¿à¤®à¤¾à¤£à¥\8b à¤¬à¤¦à¤²,$1 à¤µà¥\87र $2 à¤µà¥\87ळार à¤\95à¥\87लà¥\8dलà¥\8b",
+       "lastmodifiedat": "हà¥\87à¤\82 à¤ªà¤¾à¤¨ à¤¶à¥\87वà¤\9fà¥\80à¤\82 $1 à¤¦à¤¿à¤¸à¤¾, $2 à¤µà¥\8bराà¤\82à¤\9aà¥\8bर à¤¬à¤¦à¤²à¥\87लà¥\87à¤\82.",
        "protectedpage": "राखून दवरिल्लें पान",
        "jumpto": "हुपून वचात:",
        "jumptonavigation": "दिशा-नियंत्रण",
        "mainpage-nstab": "मुखेल पान",
        "nosuchaction": "असले तरेचे कार्य ना",
        "nosuchspecialpage": "असले कांयच विशेश पान ना",
+       "nospecialpagetext": "<strong>तुवें एक अवैद खेरीत पान मागलां.</strong>\n\nएक खेरीत पानाची वळेरी तुका हांगासर मेळूं येता [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "चूक",
        "databaseerror": "डॅटाबॅज त्रुटी",
        "databaseerror-textcl": "डॅटाबेज विरोध त्रुटी आयिल्ली आसा",
        "badtitle": "चुकीचो माथाळो",
        "badtitletext": "विनवणी केल्लें पानाचो माथाळो अवैध, रितो वा अयोग्य तरेन आंतरभाशी वा आंतर विकी माथाळ्या कडे जोडिल्लो आशिल्लो. तातूंत माथाळ्यांत वापरुं नजो अशी एक वा चड अक्षरां आसूं येतात.",
        "viewsource": "उगम पळेयात",
+       "viewsource-title": "$1‎ खातीर मूळ पळय",
+       "viewsourcetext": "तुज्यान ह्या पानाचें मूळ पळोवंक आनी नकल करुंक जाता.",
        "yourname": "वापरप्याचे नांव",
        "userlogin-yourname": "वापरप्याचे नांव",
        "userlogin-yourname-ph": "वापरप्याचे नांव घालात",
        "createacct-reason": "कारण",
        "createacct-reason-ph": "तूं दुसरें खातें कित्याक उगडटात",
        "createacct-submit": "तुमचे खातें रोचात",
-       "createacct-another-submit": "दà¥\81सरà¥\87à¤\82 à¤\96ातà¥\87à¤\82 à¤¤à¤¯à¤¾à¤° à¤\95र",
+       "createacct-another-submit": "à¤\96ातà¥\87à¤\82 à¤°à¥\8bà¤\9aात",
        "createacct-benefit-heading": "{{SITENAME}} तुमच्या सारख्या लोकांनी केल्लो",
        "createacct-benefit-body1": "{{PLURAL:$1|संपादन|संपादना}}",
        "createacct-benefit-body2": "{{PLURAL:$1|पान|पानां}}",
        "sig_tip": "वेळ-छाप सयत तुमची निशाणी",
        "hr_tip": "आडवी वळ (उणो वापरचो)",
        "summary": "आपरोस:",
-       "subject": "विशय/माथाळो",
+       "subject": "विशय:",
        "minoredit": "हें दाकटें संपादन",
        "watchthis": "हें पानार नदर दवरात",
        "savearticle": "पान सांभाळ",
        "accmailtitle": "गुपीत उतर धाडलां",
        "newarticle": "(नवें)",
        "newarticletext": "जें पान अजून अस्तित्वांत ना अशा पानाचे दुवे फाटल्यान तुमी आसात. पान रचपाक सकयले चौकटींत टायप करपाक सुरु करात (चड म्हायती खातीर [$1 आदाराचें पान] पळेयात) जर ह्या पानार तुमी चुकून पावल्यात तर ब्रावजराचो बॅक (<strong>फटीं</strong>) हो बटन दामात",
-       "noarticletext": "सदà¥\8dया à¤¹à¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\9aà¥\87र à¤\95सलà¥\80à¤\9a à¤®à¤\9cà¤\95à¥\82र à¤¨à¤¾. \nतà¥\81मà¥\80 à¤¹à¥\87र à¤ªà¤¾à¤¨à¤¾à¤\82à¤\9aà¥\87र [[Special:Search/{{PAGENAME}}|हà¥\8b à¤®à¤¾à¤¥à¤¾à¤³à¥\8b]] à¤¸à¥\8bदà¥\82à¤\82 à¤¶à¤\95तात,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} à¤¸à¤\82बà¤\82दà¥\80त à¤²à¥\89à¤\97 à¤¸à¥\8bदà¥\82à¤\82 à¤¶à¤\95तात],\nवा à¤¹à¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\95 [{{fullurl:{{FULLPAGENAME}}|action=edit}} à¤¸à¤\82पादà¥\80त] à¤\95रà¥\82à¤\82 à¤¶à¤\95तात</span>।",
+       "noarticletext": "सधà¥\8dयाà¤\95 à¤¹à¥\87à¤\82 à¤ªà¤¾à¤¨ à¤°à¤¿à¤\82तà¥\87 à¤\86सा.\nतà¥\81à¤\9cà¥\8dयान à¤¦à¥\82सऱà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤¨à¥\80 [[Special:Search/{{PAGENAME}}| à¤¹à¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\9aà¥\87 à¤¨à¤¾à¤\82व à¤¸à¥\8bदà¥\82à¤\82à¤\95 à¤\9cाता]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAME}}}} à¤¸à¤\82बà¤\82धà¥\80 à¤¸à¤¤à¥\8dरानà¥\80 à¤¸à¥\8bदà¥\82à¤\82à¤\95 à¤\9cाता], à¤µà¤¾ [{{fullurl:{{FULLPAGENAME}}|action=edit}} à¤¹à¥\87à¤\82 à¤ªà¤¾à¤¨ à¤°à¤\9aà¥\82à¤\82à¤\95 à¤\9cाता]</span>.",
        "noarticletext-nopermission": "तुर्ताक ह्या पानाचेर कसलोच मजकूर ना. तुमी हेर पानांचेर [[Special:Search/{{PAGENAME}}|ह्या माथाळ्याचो सोद]] घेवं शकतात,\nवा <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} संबंदीत लॉग सोदूं शकतात]</span>, पूण तुमकां हें पानाची रचणूक करपाची परवानगी ना।",
        "userpage-userdoesnotexist-view": "\"$1\" ह्या वापरप्याच्या खात्याची नोंदणी करूंक ना.",
        "previewnote": "'''ही फकत एक दाखवण हें मतींत दवरात.'''\nतुमचें बदल आडून राखून दवरूंक ना!",
        "hiddencategories": "हें पान {{PLURAL:$1|लिपिल्ले वर्गाचें}} आसा",
        "permissionserrorstext-withaction": "ह्या {{PLURAL:$1|कारण|कारणां}}: खातीर तुका $2 मान्यताय ना.",
        "recreate-moveddeleted-warn": "शिटकावणीः तुमी आदीं काडून उडयिल्लें पान परतून तयार करतात ह्या पानाचे फासून उडोवपी आनी दुसरे कडे व्हरपी लाग फकत सोपेपणा खातीर दिल्यात",
-       "moveddeleted-notice": "हà¥\87à¤\82 à¤ªà¤¾à¤¨ à¤\95ाडà¥\82न à¤\89डयला.\nहà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\9aà¥\8b à¤\95ाडà¥\82न à¤\89डà¥\8bवपà¥\80 à¤\86नà¥\80 à¤¹à¤¾à¤²à¥\8bवपà¥\80 à¤²à¥\89à¤\97 संदर्भा खातीर सकयल दिला.",
+       "moveddeleted-notice": "हà¥\87à¤\82 à¤ªà¤¾à¤¨ à¤\95ाडà¥\82न à¤\89डयला.\nहà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\9aà¥\87à¤\82 à¤\95ाडà¥\82न à¤\89डà¥\8bवपाà¤\9aà¥\87à¤\82, à¤°à¤¾à¤\96पाà¤\9aà¥\87à¤\82, à¤\86नà¥\80 à¤¹à¤¾à¤²à¥\8bवपाà¤\9aà¥\87à¤\82 à¤¸à¤¤à¥\8dर  संदर्भा खातीर सकयल दिला.",
        "content-model-wikitext": "विकीमजकूर",
        "content-model-text": "सादोमजकूर",
        "post-expand-template-inclusion-warning": "<strong>शिटकावणीः</strong> सांचो धरून आकार अगडबंब जाता, कांय सांच्याचो आसपाव जावचो ना.",
        "page_first": "पयलें",
        "page_last": "निमणें",
        "histlegend": "फरकाची निवडणी : पुनर्नियाळांची तुळा करपा खातीर रेडियो चौकटीं चेर कुरु करात आनी ''एंटर'' ना तर तळाकडे आशिल्लो बुतांव दामात।<br />\nविवरण : <strong>({{int:cur}})</strong> = हालींची पुनर्नियाळा बरोबर फरक, <strong>({{int:last}})</strong> = आदली पुनर्नियाळा बरोबर फरक, <strong>{{int:minoreditletter}}</strong> = दाक्टें बदल।",
-       "history-fieldset-title": "à¤\9aाळपाà¤\9aà¥\8b à¤\87तिहास",
+       "history-fieldset-title": "à¤\89à¤\9cळणà¥\8dयà¥\8b à¤\9aाळ",
        "history-show-deleted": "फकत काडून उडयिल्लें",
        "histfirst": "पोरणो",
        "histlast": "नवो ताल्ल",
        "searchprofile-advanced-tooltip": "खाशेल्या नांवथोळाणी सोदात",
        "search-result-size": "$1 ({{PLURAL:$2|1 उतर|$2 उतरां}})",
        "search-result-category-size": "{PLURAL:$1|1 सदस्य|$1 सदस्य}} ({{PLURAL:$2|1 उपगट|$2 उपगट}}, {{PLURAL:$3|1 फायल|$3 फायलीं}})",
-       "search-redirect": "(पुनर्निर्देशन $1)",
+       "search-redirect": "($1 सावन पुनर्निर्देशीत)",
        "search-section": "(विभाग $1)",
        "search-suggest": "तुमकां $1 अशें म्हणपाचें आसलें?",
        "search-rewritten": "$1 हाचो निकाल दाखयता.नाजाल्यार $2 हें सोदात.",
        "recentchanges": "हालींचे बदल",
        "recentchanges-legend": "हालींच जाल्ल्या बदलाचो विकल्प",
        "recentchanges-summary": "ह्या विकीचेर हालींच जाल्ल्या बदलांचो माग ह्या भरणांतल्यान दवरात",
+       "recentchanges-noresult": "दिलेल्या काळाचे बदल ह्या निकशाक जुळनांत.‎",
        "recentchanges-feed-description": "ह्या विकीचेर हालींच जाल्ल्या बदलांचो माग ह्या भरणांतल्यान दवरात.",
        "recentchanges-label-newpage": "ह्या संपादनांन नवें पान निर्माण केला.",
        "recentchanges-label-minor": "हें दाक्टे संपादन",
        "recentchanges-label-plusminus": "ह्या पानाचो आकार इतल्या बाइट्सन बदललो",
        "recentchanges-legend-heading": "<strong>कुंजी:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages| नव्या पानांची सुची]] पळयात)",
-       "rcnotefrom": "$2 पासून केल्ले बदल सकयल दिल्यात ($1 मेरेन दाखयल्यात)",
+       "rcnotefrom": "सकयल <strong>$3, $4</strong> सावन {{PLURAL:$5 जालेले बदल दिल्यात}} (<strong>$1</strong> मेरेन {{PLURAL:$5|दाखयलां|दाखयल्यांत}}).",
        "rclistfrom": "$3 $2 साकून नवें बदल दाखयात",
        "rcshowhideminor": "$1 दाकट्यो बदल",
        "rcshowhideminor-show": "दाखयात",
        "rcshowhidebots-show": "दाखयात",
        "rcshowhidebots-hide": "लिपयात",
        "rcshowhideliu": "$1 अधिकृत नोंदीचे वापरपी",
+       "rcshowhideliu-show": "दाखयात",
        "rcshowhideliu-hide": "लिपयात",
        "rcshowhideanons": "$1 निनांवी वापरपी",
        "rcshowhideanons-show": "दाखयात",
        "rcshowhidemine": "$1 म्हजें संपादन आंकडे",
        "rcshowhidemine-show": "दाखयात",
        "rcshowhidemine-hide": "लिपयात",
-       "rclinks": "फाà¤\9fलà¥\8dया $2 à¤¦à¤¿à¤¸à¤¾à¤\82नà¥\80 à¤\9cालà¥\8dलà¥\8b $1 बदल दाखयात",
+       "rclinks": "शà¥\87वà¤\9fà¤\9aà¥\87 $2 à¤¦à¤¿à¤¸à¤¾à¤¨à¥\80à¤\82 à¤\9cालà¥\8dलà¥\87 $1 बदल दाखयात",
        "diff": "फरक",
        "hist": "इति",
        "hide": "लिपयात",
        "minoreditletter": "द",
        "newpageletter": "न",
        "boteditletter": "र",
-       "rc-change-size-new": "$1 {{बहुवचन:$1|byte|bytes}}बदल केल्या उपरांत",
+       "rc-change-size-new": "$1 {{PLURAL:$1|बाय्ट|बाय्टी}} बदल केल्या उपरांत",
        "rc-enhanced-expand": "म्हायती दाखय",
        "rc-enhanced-hide": "म्हायती लिपय",
        "recentchangeslinked": "संबंदित बदल",
        "recentchangeslinked-toolbox": "संबंदीत बदल",
        "recentchangeslinked-title": "\"$1\" च्या संबंदातले बदल",
-       "recentchangeslinked-summary": "à¤\96ाशà¥\87लà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\82 à¤\95डलà¥\8dयान à¤¦à¥\81वà¥\87 à¤®à¥\87ळिलà¥\8dलà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\82मदà¥\80à¤\82 (वा à¤µà¤¿à¤¶à¤¿à¤¶à¥\8dà¤\9f à¤µà¤°à¥\8dà¤\97ाà¤\82à¤\9aà¥\8dया à¤µà¤¾à¤\82à¤\97डà¥\8dयाà¤\82मदà¥\80à¤\82) à¤¹à¤¾à¤²à¥\80à¤\82à¤\9a à¤\95à¥\87लà¥\8dलà¥\8dया à¤¬à¤¦à¤²à¤¾à¤\82à¤\9aà¥\80 à¤¹à¥\80 à¤µà¤³à¥\87रà¥\80. [[Special:Watchlist|तà¥\81मà¤\9aà¥\8dया à¤¸à¤¾à¤¦à¥\81रवळà¥\87रà¥\80]] à¤ªà¤¾à¤¨à¤¾ '''ठळà¤\95''' à¤¦à¤¾à¤\96यलà¥\8dयात",
+       "recentchangeslinked-summary": "à¤\8fà¤\95ा à¤ªà¤¾à¤¨à¤¾à¤\9aà¥\87à¤\82 à¤¨à¤¾à¤\82व à¤¬à¤°à¤¯ à¤\9cà¥\8dया à¤µà¤°à¥\8dवà¥\80à¤\82 à¤\9cडलà¥\87लà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\82à¤\9aà¥\87र à¤µà¤¾ à¤ªà¤¾à¤¨à¤¾à¤\82 à¤ªà¤¾à¤¸à¥\82न à¤¬à¤¦à¤² à¤¦à¤¿à¤¸à¤¤à¤²à¥\8b. (à¤\8fà¤\95ा à¤µà¤°à¥\8dà¤\97णाà¤\9aà¥\87 à¤µà¤¾à¤\82à¤\97डà¥\80 à¤ªà¤³à¥\8bवà¤\82à¤\95, à¤¬à¤°à¤¯ {{ns:category}}:वरà¥\8dà¤\97ाà¤\9aà¥\87à¤\82 à¤¨à¤¾à¤\82व). [[Special:Watchlist|तà¥\81à¤\9cà¥\8dया à¤¸à¤¾à¤¦à¥\81रवळà¥\87रà¥\80à¤\82त]] à¤\86सलà¥\87लà¥\8dया à¤ªà¤¾à¤¨à¤¾à¤\9aà¥\87र à¤¬à¤¦à¤² <strong>दाà¤\9f</strong> à¤\86सात.",
        "recentchangeslinked-page": "पानाचें नांव",
        "recentchangeslinked-to": "ह्या पाना बदला दिल्ल्या पानांक जडून आशिल्ल्या पानांचे बदल दाखय",
        "upload": "फायल अपलोड करात",
        "license-header": "परवांगी",
        "listfiles-delete": "काडून उडयात",
        "imgfile": "फायल",
+       "listfiles": "Faylichi volleri‎",
        "listfiles_date": "तारीख",
        "listfiles_name": "नांव",
        "listfiles_user": "वापरपी",
        "filehist-dimensions": "परिमाण",
        "filehist-comment": "शेरो",
        "imagelinks": "फायलिचो वापर",
-       "linkstoimage": "हे फायलीक सकयल दिल्ल्यो पानाच्यो जोडण्यो {{PLURAL:$1|आसात}}.",
-       "nolinkstoimage": "हà¥\87 à¤«à¤¾à¤¯à¤²à¥\80à¤\95 à¤¦à¥\81वà¥\8b à¤\86शिलà¥\8dलà¥\80à¤\82 à¤\86नà¥\80à¤\95 à¤ªà¤¾à¤¨à¤¾à¤\82 à¤¨à¤¾त.",
+       "linkstoimage": "{{PLURAL:$1|हें पान|$1 हीं पानां}} ही फायल {{PLURAL:$1|वापरता|वापरतात}}:",
+       "nolinkstoimage": "हà¥\8dया à¤«à¤¾à¤¯à¤²à¥\80à¤\95 à¤µà¤¾à¤ªà¤°à¤¤à¤¾à¤¤ à¤¤à¤¸à¤²à¥\80à¤\82 à¤ªà¤¾à¤¨à¤¾à¤\82 à¤¨à¤¾à¤\82त.",
        "sharedupload-desc-here": "ही फयल $1 हांगाची आनी ती हे प्रकल्पां खातीर वापरल्यार चलता. (तिच्या $2 ह्या फयलींतलें वर्णनाचे पान) तातूंतलें वर्णन सकयल दिलां.",
        "upload-disallowed-here": "तूं ह्या फायलीचेर अधिलेखीत करूंक शकना",
        "randompage": "खंयचेंय पान",
        "dellogpage": "काडून उडयिल्ल्यांची वळेरी",
        "rollbacklink": "फाटीं घेयात",
        "rollbacklinkcount": "$1 {{PLURAL:$1|संपादन}} फाटीं घेयात",
-       "changecontentmodel-title-label": "पानाचो माथाळो",
+       "changecontentmodel-title-label": "पानाचो माथाळो:",
        "changecontentmodel-reason-label": "कारण:",
        "protectlogpage": "सुरक्षितेचें सोत्र",
        "protectedarticle": "राखिल्ले\"[[$1]]\"",
        "contributions-title": "$1 खातीर वापरप्याचीं योगदानां",
        "mycontris": "योगदान",
        "anoncontribs": "योगदान",
+       "contribsub2": "{{GENDER:$3|$1}} हाच्यो ($2)",
        "uctop": "हालीचें",
        "month": "ह्या म्हयन्या सावन (आनी आदलें):",
        "year": "ह्या वर्सा सावन (आनी आदलें):",
        "sp-contributions-search": "योगदानां सोदात",
        "sp-contributions-username": "आयपी नामो वा वापरप्याचें नांव",
        "sp-contributions-toponly": "फकत सगळ्यांत हालींचे पुनर्नियाळ आशिल्लीं संपादन दाखयात",
+       "sp-contributions-newonly": "फकत तसलेचच बदल दाखय, जांचे वर्वीं पान रचलां",
        "sp-contributions-submit": "सोद",
        "whatlinkshere": "हाका कितें जडता",
        "whatlinkshere-title": " \"$1\" हाका दुवे आशिल्लीं पानां",
        "linkshere": "मुखावेली पानां <strong>$2</strong>: हाका जडतात",
        "nolinkshere": "<strong>$2</strong> हाका खंयच्याच पानाचो दुवो ना",
        "isredirect": "पुनर्निर्देशन पान",
-       "istemplate": "$1 दूसरात-समावेस",
+       "istemplate": "दुरास्थ-समावेस",
        "isimage": "फायलीचो दुवो",
        "whatlinkshere-prev": "{{PLURAL:$1|आदलें|आदलीं $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|फुडलें|फुडलें $1}}",
        "whatlinkshere-links": "← दुवे",
        "whatlinkshere-hideredirs": "$1 पुनर्निर्देशन",
-       "whatlinkshere-hidetrans": "$1 à¤¦à¥\82सà¥\8dरात-समावà¥\87श",
+       "whatlinkshere-hidetrans": "$1 à¤¦à¥\81रासà¥\8dथ-समावà¥\87स",
        "whatlinkshere-hidelinks": "$1 दुवे",
        "whatlinkshere-hideimages": "$1 फायल दुवे",
        "whatlinkshere-filters": "गाळणे",
-       "ipboptions": "2 वरां: 2hours ,1 दीस:1 day,3 दीस:3 days,1 सुमान:1 week,2 सुमनां:2 weeks,1 म्हयनो:1 month,3 म्हयने:3 months,6 म्हयने:6 months,1 वर्स:1 year,अनिश्चीत:infinte",
+       "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",
        "ipblocklist": "आडायल्लें वापरपी",
        "blocklink": "आडावणी",
        "change-blocklink": "विभाग सुदारप",
        "allmessagesdefault": "पूर्वनिर्धारित संदेशाचो मजकूर",
        "thumbnail-more": "व्हड करात",
        "thumbnail_error": "$1ः लघुप्रतिमा करतांनाची चूक",
-       "tooltip-pt-userpage": "तुमचें वापरपाचें पान",
-       "tooltip-pt-mytalk": "तुमचें चर्चेचें पान",
+       "tooltip-pt-userpage": "{{GENDER:|तुमचें वापरप्याचें}} पान",
+       "tooltip-pt-mytalk": "{{GENDER:|तुमचें}} भासाभासाचें पान",
        "tooltip-pt-preferences": "{{GENDER:|तुमची}} पसंती",
        "tooltip-pt-watchlist": "तुमी बदल करपा खातीर देखरेख करतात त्या पानांची वळेरी",
-       "tooltip-pt-mycontris": "तुमच्या योगदानांची वळेरी",
+       "tooltip-pt-mycontris": "{{GENDER:|तुमच्या}} योगदानांची वळेरी",
        "tooltip-pt-login": "सत्रारंभ करप बरें, पूण तशी सक्ती ना.",
        "tooltip-pt-logout": "सत्र शेवट",
        "tooltip-pt-createaccount": "तुमी खातें उगडून सत्रारंभ करचें अशें सुचयतात, पूण तें सक्तीचें ना.",
        "tooltip-t-whatlinkshere": "हांगा दुवे आशिल्ल्या सगळ्या विकी पानांची वळेरी",
        "tooltip-t-recentchangeslinked": "ह्या पानावेल्यान दुवे दिल्ल्या पानांतले हालींचे बदल",
        "tooltip-feed-atom": "ह्या पाना खातीर ऍटम पूर्वण",
-       "tooltip-t-contributions": "ह्या वापरप्याची योगदानाची वळेरी",
-       "tooltip-t-emailuser": "ह्या उपेगकर्त्याक इ-मेल धाडात",
+       "tooltip-t-contributions": "{{GENDER:$1|ह्या वापरप्याची}} योगदानाची वळेरी",
+       "tooltip-t-emailuser": "{{GENDER:$1|ह्या उपेगकर्त्याक}} इ-मेल धाडात",
        "tooltip-t-upload": "फायली अपलोड करात",
        "tooltip-t-specialpages": "सगळ्या विशेश पानांची वळेरी",
        "tooltip-t-print": "ह्या पानाची छापपायोग्य आवृत्ती",
        "tooltip-t-permalink": "ह्या पानाच्या ह्या पुनर्नियाळाकडे सदांकाळ दुवो",
        "tooltip-ca-nstab-main": "मजकूर पान पळेयात",
        "tooltip-ca-nstab-user": "वापरप्याचें पान दाखय",
-       "tooltip-ca-nstab-special": "हà¥\87à¤\82 à¤\96à¥\87रà¥\80त à¤ªà¤¾à¤¨, à¤¤à¥\81मà¤\9aà¥\8dयाà¤\82नà¥\80à¤\82 à¤\96à¥\81दà¥\8dद à¤¤à¥\8dया à¤ªà¤¾à¤¨à¤¾à¤° à¤¸à¤\82सà¥\8dà¤\95रण à¤\95रà¥\82à¤\82 à¤¨à¤\9cà¥\8b",
+       "tooltip-ca-nstab-special": "हà¥\87à¤\82 à¤\8fà¤\95 à¤\96à¥\87रà¥\80त à¤ªà¤¾à¤¨, à¤\86नà¥\80 à¤¹à¥\87à¤\82 à¤¬à¤¦à¤²à¥\82à¤\82à¤\95 à¤\9cायना",
        "tooltip-ca-nstab-project": "प्रकल्पाचें पान पळेयात",
        "tooltip-ca-nstab-image": "फायलीचें पान पळेयात",
        "tooltip-ca-nstab-template": "सांचो पळेयात",
        "tooltip-rollback": "निमाण्या योगदान करप्यान ह्या पानाचेर केल्लें संपादन रोलबॅक  (फाटीं घेयात) एकाच क्लीकान मूळ पदार हाडटा",
        "tooltip-undo": "\"आदलें स्थितीर हाडचें\" ह्या बदलाक परत व्हरुन संपादन स्थितीन झलक रितीन दाखयतात.\nहाचेवरवीं सारांशान आदल्या स्थितीर हाडपाचें कारण बरोवं शकता.",
        "tooltip-summary": "आपरोसाची नोंदणी करात",
-       "simpleantispam-label": "à¤\8fनà¥\8dà¤\9fà¥\80-सà¥\8dपà¥\88म à¤¤à¤ªà¤¾à¤¸à¤ª.\nहà¥\87 à¤­à¤°à¥\80<strong>नà¤\95ाय</strong>!",
+       "simpleantispam-label": "सà¥\8dपमविरà¥\82ध à¤¤à¤ªà¤¾à¤¸à¤£à¥\80.\nहà¥\87à¤\82 à¤­à¤° <strong>नाà¤\95ा</strong>!",
        "pageinfo-toolboxlink": "पानाची म्हायती",
        "pageinfo-contentpage-yes": "हय",
        "previousdiff": "← आदलें संपादन",
        "tag-filter": "[[Special:Tags|कुर्वेचीट]] गाळणो:",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|कुरवेचीट|कुरवेचीटी}}]]: $2",
        "tags-active-yes": "हय",
+       "tags-active-no": "ना",
        "htmlform-title-not-exists": "$1 अस्तित्वांत ना.",
        "logentry-delete-delete": "$1 {{GENDER:$2|काडून उडयल्ले पान}} $3",
        "logentry-move-move": "$1 हाणें $3 पानाक $4 {{GENDER:$2|हालयला}}",
index 83a633a..9f1f706 100644 (file)
@@ -17,7 +17,7 @@
        "tog-enotifwatchlistpages": "Mhojea sadurvollerintlem pan vo fayl bodol'li zalear mhaka email dhadd",
        "tog-shownumberswatching": "Nodor dovorpi vaporpeanche sonkhya dakhoi",
        "tog-oldsig": "Tujea sod'dheachi soy:",
-       "tog-uselivepreview": "Pan porot ugdinastana zolok dahkoi",
+       "tog-uselivepreview": "Pan porot ughoddnastana zholok dakhoi",
        "tog-watchlisthideown": "Sadurvollerint mhoje bodol lipoi",
        "tog-watchlisthidebots": "Sadurvollerint robotani kel'le bodol lipoi",
        "tog-watchlisthideminor": "Sadurvollerint dhaktem bodol lipoi",
        "listingcontinuesabbrev": "chalu",
        "index-category": "Suchi-potran zodlelim panam",
        "noindex-category": "Suchi-potran zoddunk-naslelim panam",
-       "broken-file-category": "Tuttlolea faylinchea duve aslelim panam‎",
+       "broken-file-category": "Tuttlolea faylinche duve aslelim panam‎",
        "about": "Hea vixoiavoir",
        "article": "Vixoi sombondhi pan",
        "newwindow": "(novea zonelant uktem zata)",
        "redirectedfrom": "($1 savn punornirdexit)",
        "redirectpagesub": "Punornirdexan pan",
        "redirectto": "Hanga ponornirdeshit:",
-       "lastmodifiedat": "Hem pan xevtim $1 disa, $2 vazta bodolelem.",
+       "lastmodifiedat": "Hem pan xevttim $1 disa, $2 vorancher bodololem.",
        "protectedpage": "Rakhun dovorl'lem pan",
        "jumpto": "Hupun voch",
        "jumptonavigation": "dixa-niontronn",
        "youhavenewmessages": "Tumkam $1 ($2) asat.",
        "youhavenewmessagesfromusers": "Tuka {{PLURAL:$3|ek vaporpi|$3 vaporpi}} koddlean $1 {{PLURAL:$4|asa|asat}} ($2).‎",
        "newmessageslinkplural": "{{PLURAL:$1|novo sondex|999=nove sondex}}‎",
-       "newmessagesdifflinkplural": "{{PLURAL:$1|nimanno bodol|999=nimanneo bodol}}",
+       "newmessagesdifflinkplural": "{{PLURAL:$1|nimanno bodol|999=nimanne bodol}}",
        "youhavenewmessagesmulti": "$1 cher tuka noveo sondex asat",
        "editsection": "bodol",
        "editold": "bodol",
        "databaseerror-textcl": "Totv-kox (database) sodtana chuk ghodli",
        "databaseerror-query": "Anurodh: $1",
        "databaseerror-error": "Chuk: $1",
-       "missing-article": "Totv-kox (Database) hantun mellunk zai aslem tem mozkur \"$1\" $2 mellunk-nam.\n\nHorxim, oxem ek pornem frk vo eka panachea itihasacho duvo kadun udoila, tedna zata.\n\nOxem nhoi zalear, tuka softwer-an chuk sampodlam zait.\nUpkar korun eka [[Special:ListUsers/sysop|karbhari]]chea nodrek hadd, Internet Zago Sodpi (URL) hachi nond gheun.",
+       "missing-article": "Totv-kox (Database) hantun mellunk zai aslem tem mozkur \"$1\" $2 mellunk-nam.\n\nHorxim, oxem ek pornem frk vo eka panachea itihasacho duvo kaddun uddoila, ten’na zata.\n\nOxem nhoi zalear, tuka software-ant chuk sampoddlea zait.\nUpkar korun eka [[Special:ListUsers/sysop|karbhari]]chea nodrek hadd, Ontorzalleant Zago Sodpi (URL) hachi nond ghevn.",
        "missingarticle-rev": "(uzollnni#: $1)",
        "missingarticle-diff": "(Frk: $1, $2)",
        "badtitle": "Chukichem nanv",
        "loginerror": "Sotrorombhachi truti",
        "createacct-error": "Khatem rochtanam truti",
        "createaccounterror": "Khatem rochunk zaunk na: $1",
-       "loginsuccesstitle": "Sotrorombh zalem",
+       "loginsuccesstitle": "Sotrarombh zalem",
        "nosuchusershort": "\"$1\" hea nanvan konn vapurpi na.\nNanv boroitana chuk zali gai?",
        "nouserspecified": "Vapurpeachem nanv diunk-uch zai.",
        "login-userblocked": "Hea vapurpeak addaila. Sotrorombh korunk zaina.",
        "emaildisabled": "Hi site mail dhadpak xokona.",
        "accountcreated": "Khatem rochlem.",
        "createaccount-title": "{{SITENAME}} -ak khatem rochlem",
-       "login-abort-generic": "Tujem sotrorombh opexi tharlam - Nixfolit",
+       "login-abort-generic": "Tujem sotrorombh opexi tharlam - Nixfollit",
        "login-migrated-generic": "Tujem khatem stholontrit zalam ani vapurpeachem nanv hea wikicher anink ostitvant na.",
        "loginlanguagelabel": "Bhas: $1",
        "pt-login": "Sotrorombh",
        "passwordreset-domain": "Domain:",
        "passwordreset-email": "Email potto:",
        "passwordreset-emailelement": "Vapurpeachem nanv: \n$1\n\nTatpurtem gupitutor: \n$2",
-       "passwordreset-emailsentemail": "Ho email pot'to tujea kontak zodlelem asa zalear, gupitutor portun tharaipacho email dhadlelem zatelem.",
-       "changeemail": "Email potto bodol vo kad",
+       "passwordreset-emailsentemail": "Ho email pot'to tujea hixobant zoddlolo asa zalear, gupitutor portun tharavpacho email dhaddlelem zatelem.",
+       "changeemail": "Email po’tto bodol vo kadd",
        "changeemail-oldemail": "Sodhyacho email potto:",
        "changeemail-newemail": "Novo email potto:",
        "changeemail-none": "(kai na)",
        "anoneditwarning": "<strong>Chotrai:</strong> Tuven sotrorombh korunk nai. Tu bodol korit zalear tuzo IP pot'to soglleank polleunk zatelem. Tu <strong>[$1 sotrorombh korit]</strong> vo <strong>[$2 kont rochit]</strong> zalear, tuje bodol tuzo vaporpeachem nanvak zoddteleo ani anik-ui faide asat.",
        "missingcommenttext": "Upkar korun tuzo xero boroi.",
        "blockedtitle": "Vapurpeak addaila",
-       "blockedtext": "<strong>Tujem vaporpeachem nanv vo IP pot'to addavpant aila.</strong>\n\nAddavop $1 hannem kelam.\nKaronn dilam tem <em>$2</em>.\n\n* Addavpachi survat: $8\n* Addavpachea somp’pacho vell: $6\n* Addavpak ievjila: $7\n\nTujean $1-ak vo dusrea [[{{MediaWiki:Grouppage-sysop}}|karbhariak]] addavnne bodol bhasabhas korunk sompork korunk zata. Tujean \"{{int:emailuser}}\" sobhavgunn vaprunk zaina kheriz ek void email pot'to tujea [[Special:Preferences|khatem posontint]] nischit kelea xivai ani tuka tem vaporpak addavnk na zalear. Tuzo chalont IP pot'to asa $3, ani addavnnecheo ank #$5 asa. Soglleo voileo bariksanno tum kortai tea vicharant somavex kor.",
+       "blockedtext": "<strong>Tujem vaporpeachem nanv vo IP pot'to addavpant aila.</strong>\n\nAddavop $1 hannem kelam.\nKaronn dilam tem <em>$2</em>.\n\n* Addavpachi survat: $8\n* Addavop sompovpacho vell: $6\n* Addavpak ievjila: $7\n\nTujean $1-ak vo dusrea [[{{MediaWiki:Grouppage-sysop}}|karbhariak]] addavnne bodol bhasabhas korunk sompork korunk zata. Tujean \"{{int:emailuser}}\" sobhavgunn vaprunk zaina kheriz ek void email pot'to tujea [[Special:Preferences|khatem posontint]] nischit kelea xivai ani tuka tem vaporpak addavnk na zalear. Tuzo chalont IP pot'to asa $3, ani addavnnecheo ank #$5 asa. Soglleo voileo bariksanno tum kortai tea vicharant somavex kor.",
        "blockednoreason": "Kainch karonn diunk na",
        "loginreqtitle": "Sotrorombh gorjechem",
        "loginreqlink": "sotrorombh kor",
        "newarticle": "(Novem)",
        "newarticletext": "Tuven ek duveche patlav kelai, zachem pan azun rochunk na.\nPan rochunk, khallchea chovkottan boroi (anik mahitik [$1 adar pan] polloi).\nTu hangasor chukin pavlai zalear tujea internet browser-achi <strong>Fatim</strong> vo <strong>Back</strong> butao dab.",
        "anontalkpagetext": "----\n<em>Hem bhasabhasechem pan ek ninami vaporpeak zannem ozun ek khatem ugddunk na, vo to tem vaporna.</em>\nHea khatir amkam ankddeancho IP pot'to vaprunk podta taka vollkhunk.\nToslo IP pot'to sabar vaporpeamni vaprum ieta.\nTum zor ek ninami vaporpi asa ani tuka dista ki sombondit xere tuje vixim keleat, upkar korun [[Special:CreateAccount|ek khatem roch]] vo [[Special:UserLogin|log in]] fuddle guspop ninami vaporpeanchem tallunk.‎",
-       "noarticletext": "Sodheak hem pan rinte asa.\nTujean dusrea panani [[Special:Search/{{PAGENAME}}|hea panache nanv sodunk zata]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sombondhi sotrani sodunk zata], vo [{{fullurl:{{FULLPAGENAME}}|action=edit}} hem pan rochunk zata]</span>.",
-       "noarticletext-nopermission": "Sodheak hem pan rinte asa.\nTujean dusrea panani [[Special:Search/{{PAGENAME}}|hea panache nanv sodunk zata]], vo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sombondhi sotrani sodunk zata], pun tuka hem pan rochunk porvangi na.",
+       "noarticletext": "Sodheak hem pan ritem asa.\nTujean dusrea panani [[Special:Search/{{PAGENAME}}|hea panache nanv sodunk zata]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sombondhi sotrani sodunk zata], vo [{{fullurl:{{FULLPAGENAME}}|action=edit}} hem pan rochunk zata]</span>.",
+       "noarticletext-nopermission": "Sodheak hem pan ritem asa.\nTujean dusrea panani [[Special:Search/{{PAGENAME}}|hea panache nanv sodunk zata]], vo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sombondhi sotrani sodunk zata], pun tuka hem pan rochunk porvangi na.",
        "userpage-userdoesnotexist-view": "\"$1\" hea vapurpeachea khateachi nondnni korunk na.",
        "clearyourcache": "<strong>Note:</strong> Samball’llea uprant, tuka ghoddiek tujea browseracho cache koddsoravnk poddot bodol pollonvche khatir.\n* <strong>Firefox / Safari:</strong> <em>Shift</em> dhor <em>Reload</em> klik kortana, vo dam <em>Ctrl-F5</em> vo <em>Ctrl-R</em> (<em>⌘-R</em> Mac-acher)\n* <strong>Google Chrome:</strong> <em>Ctrl-Shift-R</em> dam (<em>⌘-Shift-R</em> eka Mac-acher)\n* <strong>Internet Explorer:</strong> <em>Ctrl</em> dhor <em>Refresh</em> klik kortana, vo dam <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Hanga voch: <em>Menu → Settings</em> (<em>Opera → Preferences</em> Mac-acher) ani uprant <em>Privacy & security → Clear browsing data → Cached images and files</em>.‎",
        "previewnote": "<strong>Hem fokot ek zholok mhonn ugddas dhor.</strong>\nTuje bodol azun sambhallun dovrunk nant!",
        "permissionserrors": "Porvangechi chuk",
        "permissionserrorstext-withaction": "$2, hem korpak tuka porvangi na, {{PLURAL:$1|hea karnnak lagon|hea karnnank lagun}}:",
        "recreate-moveddeleted-warn": "<strong>Xittkavnni: Tum ek pan porot rochtai jem fattim kadun udoilelem.<strong>\n\nPanacho sompadon korop sarkem zalear dhean di.\nPan kadoupachem ani halovpachem sotr, sovloti khatir hangasor dilelem asa:",
-       "moveddeleted-notice": "Hem pan kadun udoilelem asa.\nPanachea kadun udounechi, rakhpachi, ani hallovnechi sotr sondorba khatir sokoil dilea.",
+       "moveddeleted-notice": "Hem pan kaddun udoilelem asa.\nPanachem kaddun uddovpachem, rakhpachem, ani halovpachem sotr sondhorba khatir sokoil dila.",
        "content-model-wikitext": "wikimozkur",
        "content-model-text": "Sado mozkur",
        "post-expand-template-inclusion-warning": "<strong>Chotrai:</strong> Sancho zoddpacho akar chod vhodlem asa.\nThodde sache zoddchenant.",
        "page_last": "akhirchem",
        "histlegend": "Frk nivoddni: Jeo uzollneo tuka comparar korunk zai, tenche fudle ''radio'' butao petoi ani ''Enter'' nazalear khalcho butao dab.<br />\nVivron: <strong>({{int:cur}})</strong> = halinchi uzollnie borobor forok, <strong>({{int:last}})</strong> = adli uzollnie borobor forok, <strong>{{int:minoreditletter}}</strong> = dhaktem bodol.",
        "history-fieldset-title": "Uzollnneo chall",
-       "history-show-deleted": "Fokot uzollnni kadun udoilelem",
+       "history-show-deleted": "Fokot uzollnni kadun uddoilolem",
        "histfirst": "sogleavon adhlem",
        "histlast": "sogleavon novem",
        "history-feed-title": "Uzollnniancho itihas",
        "revdel-restore": "Disnnem bodol",
        "pagehist": "Panacho itihas",
        "mergehistory-reason": "Karonn:",
-       "mergelog": "Vilin korpacho sotr",
+       "mergelog": "Vilin korpachem sotr",
        "revertmerge": "Doxim kor",
        "history-title": "\"$1\" hachea uzollnnecho itihas",
        "difference-title": "\"$1\"-chea avrutint ontor",
        "prefs-help-email": "Email potto sokticho na, pun tum gupitutor visroxi zalear gupitutor punorsthapon korunk email pottechi goroz podta.",
        "prefs-help-email-others": "Tujean dusreank tujea vapurpeacho panar vo bhasabhasache panar aslele eke email duve vorvim tuje xim sompork korunk diunk zata.\nDusre tuje xim sompork kortat tednam tuzo email potto tankam kollchenam.",
        "userrights-user-editname": "Ek vapurpeachem nanv ghal:",
-       "group-bot": "Robotam",
+       "group-bot": "Robottam",
        "group-sysop": "Karbhari",
        "group-all": "(soglle)",
        "grouppage-bot": "{{ns:project}}:Robotam",
        "right-move": "Panam haloi",
        "right-writeapi": "Borovpeache API-cho upeog",
        "newuserlogpage": "Vapurpi rochnnechem sotr",
-       "rightslog": "Vaporpeachea hokancho sotr",
+       "rightslog": "Vaporpeachea hokanchem sotr",
        "action-edit": "hem pan sudar",
        "action-createaccount": "hem vaporpeachem khatem roch",
        "nchanges": "$1 {{PLURAL:$1|bodol}}",
        "rcfilters-activefilters": "Kriaxil challnneo",
        "rcfilters-activefilters-hide": "Lipoi",
        "rcfilters-activefilters-show": "Dakhoi",
+       "rcfilters-activefilters-hide-tooltip": "Kriaxil challnnecho kxetr lipoi",
+       "rcfilters-activefilters-show-tooltip": "Kriaxil challnneache kxetr dakhoi.",
        "rcfilters-advancedfilters": "Sudarit challnneo",
        "rcfilters-limit-title": "Dakhovpache porinnam",
        "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|bodol}}, $2",
+       "rcfilters-date-popup-title": "Soda khatir vellacho kall",
        "rcfilters-days-title": "Halinche dis",
        "rcfilters-hours-title": "Halinchim voram",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|dis}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|vor|voram}}",
        "rcfilters-quickfilters": "Samball’lleleo challnneo",
+       "rcfilters-quickfilters-placeholder-title": "Ozun poriant khoinchich challnni samballunk na",
        "rcfilters-savedqueries-defaultlabel": "Samball’lleleo challnneo",
        "rcfilters-savedqueries-rename": "Nanv bodol",
        "rcfilters-savedqueries-setdefault": "Default koxem bosoi",
        "rcfilters-savedqueries-new-name-label": "Nanv",
        "rcfilters-savedqueries-apply-label": "Challnni roch",
        "rcfilters-savedqueries-cancel-label": "Rod'd kor",
+       "rcfilters-savedqueries-add-new-title": "Chalont challnnechi manddavoll samball",
+       "rcfilters-savedqueries-already-saved": "Heo challnneo adinch samball’lloleo asat. Ek novi Samball’lloli Challnni rochunk, tujeo manddavolleo bodol.",
+       "rcfilters-restore-default-filters": "Default challnneo porot hadd",
+       "rcfilters-clear-all-filters": "Soglleo challnneo nivoll kor",
        "rcfilters-show-new-changes": "$1 savn noveo bodol polloi",
        "rcfilters-search-placeholder-mobile": "Challnneo",
        "rcfilters-invalid-filter": "Ovoid challnni",
        "rcfilters-filter-editsbyother-label": "Dusreanim kel'le bodol",
        "rcfilters-filter-editsbyother-description": "Tuje khas bhairavn, soglle bodol",
        "rcfilters-filtergroup-user-experience-level": "Vaporpeachi nondnni ani onnbhov",
+       "rcfilters-filter-user-experience-level-registered-description": "Sotrarombh zalole sompadok.",
+       "rcfilters-filter-user-experience-level-learner-label": "Xikpi",
+       "rcfilters-filter-user-experience-level-experienced-label": "Onnbhovi vaporpi",
        "rcfilters-filtergroup-automated": "Apoap zalolem iogdan",
        "rcfilters-filter-bots-label": "Robot",
        "rcfilters-filter-bots-description": "Apoap avtamni kelolem sompadon",
        "rcfilters-filter-humans-label": "Monxan kelolem (nhoi robotan)",
        "rcfilters-filter-humans-description": "Monxani kelolem sompadon",
-       "rcfilters-filter-reviewstatus-unpatrolled-description": "Paro kela mhonn khunnavnk naslolem sompadon.",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Paro kela mhonn hatan vo apoap khunnavnk naslolem sompadon.",
+       "rcfilters-filter-reviewstatus-unpatrolled-label": "Paro korunk naslolem",
        "rcfilters-filtergroup-significance": "Mhotv",
        "rcfilters-filter-minor-label": "Dhakte bodol",
        "rcfilters-filter-minor-description": "Borovpean dhaktem mhonn khunne chitt kelolem sompadon",
        "rcfilters-filter-major-label": "Dhakte naslolem sompadon",
        "rcfilters-filter-major-description": "Dhaktem mhonn khunne chitt korunk naslolem sompadon",
        "rcfilters-filtergroup-watchlist": "Sadurvollerintlim panam",
-       "rcfilters-filter-watchlist-watchednew-description": "Bodol ghoddlea uprant tuvem bhett divnk na tea sadurvollerintlim panache bodol.",
+       "rcfilters-filter-watchlist-watched-label": "Sadurvollerintlim",
+       "rcfilters-filter-watchlist-watchednew-label": "Sadurvolleriche nove bodol",
+       "rcfilters-filter-watchlist-watchednew-description": "Bodol ghoddleat ten’na savn tuvem bhett divnk nant tea sadurvollerintlim panache bodol.",
+       "rcfilters-filter-watchlist-notwatched-label": "Sadurvollerintlim nhoi",
+       "rcfilters-filter-watchlist-notwatched-description": "Sadurvollerichim panank bodol soddun her sogllem.",
        "rcfilters-filtergroup-watchlistactivity": "Sadurvollerichem kario",
        "rcfilters-filter-watchlistactivity-unseen-label": "Pollovnk naslole bodol",
        "rcfilters-filter-watchlistactivity-unseen-description": "Bodol ghoddlea uprant tuvem bhett divnk na tea panache bodol.",
        "rcfilters-filter-watchlistactivity-seen-description": "Bodol ghoddlea uprant tuvem bhett dilolea tea panache bodol.",
        "rcfilters-filtergroup-changetype": "Bodolacho prokar",
        "rcfilters-filter-pageedits-label": "Panacheo sompadonam",
+       "rcfilters-filter-newpages-label": "Panam rochop",
+       "rcfilters-filter-newpages-description": "Sompadon jim novim panam rochtat",
        "rcfilters-filter-categorization-label": "Vorgache bodol",
        "rcfilters-filter-categorization-description": "Vorgant savn pana zoddloleachi vo kaddloleachi nond",
        "rcfilters-filter-logactions-label": "Sotran nond zal’leo kario",
        "rcfilters-filtergroup-lastrevision": "Akherchim uzollnnim",
        "rcfilters-filter-lastrevision-label": "Sogleanvon novi uzollnni",
        "rcfilters-filter-lastrevision-description": "Ek panak fokot nimannem bodol",
+       "rcfilters-filter-previousrevision-label": "Halinchi uzollnni nhoi",
        "rcfilters-filter-previousrevision-description": "Soglle bodol je \"halinchi uzollnni\" nant.",
        "rcfilters-tag-prefix-namespace-inverted": "$1 <strong>:nhoi</strong>",
        "rcfilters-view-tags": "Khunnechittichem sompadon",
+       "rcfilters-view-tags-tooltip": "Sompadonacheo khunne chitti vaprun porinnam chall",
        "rcfilters-view-tags-help-icon-tooltip": "Khunnechittichem sompadona babtint odik xikun ghe",
+       "rcfilters-watchlist-markseen-button": "Soglle bodol polleleat mhonn khunnai.",
+       "rcfilters-watchlist-showupdated": "Bodol zal'leak savn je panank tuvem bhett dinvk na, te bodol <strong>datt</strong> okxoramni, ani ghott khunnamni dileat.",
+       "rcfilters-filter-showlinkedto-label": "Panak zoddtat tea panache bodol dakhoi",
        "rcfilters-target-page-placeholder": "Ek panache nanv ( vo vorg) ghal",
        "rcnotefrom": "Sokoil <strong>$3, $4<strong> savn {{PLURAL:$5|zalelem bodol dilam|zalelem bodol dileant}} (<strong>$1<strong> meren {{PLURAL:$5|dakhoilam|dakhoileant}}).",
        "rclistfrom": "$3 $2 savn suru zatelim nove bodol dakhoi",
        "rcshowhidemine": "Mhoje bodol $1",
        "rcshowhidemine-show": "Dakhoi",
        "rcshowhidemine-hide": "Lipoi",
-       "rclinks": "Xevtiche $2 disanim zal'le $1 bodol dakhoi",
+       "rclinks": "Xevottche $2 disamni zal'le $1 bodol dakhoi",
        "diff": "frk",
        "hist": "iti",
        "hide": "Lipoi",
        "rc-change-size-new": "$1 {{PLURAL:$1|byte|byti}} bodol kel'lea uprant",
        "rc-enhanced-expand": "Bariksann dakhoi",
        "rc-enhanced-hide": "Bariksann lipoi",
-       "rc-old-title": "orombhant rochloli \"$1\" hea nanvan‎.",
+       "rc-old-title": "arombhant rochloli \"$1\" hea nanvan‎",
        "recentchangeslinked": "Sombondit bodol",
        "recentchangeslinked-feed": "Sombondit bodol",
        "recentchangeslinked-toolbox": "Sombondit bodol",
        "recentchangeslinked-title": "\"$1\"che sombondit bodol",
-       "recentchangeslinked-summary": "Eka panachem nanv boroi jea vorvim zoddlolea panancher vo panam pasun bodol  distolo. (Eka vorgonnache vangddi pollovnk, boroi {{ns:category}}:Vorgonnachem nanv). [[Special:Watchlist|Tujea sadurvollerint]] aslelim panacher bodol <strong>datt</strong> asat.",
+       "recentchangeslinked-summary": "Eka panachem nanv boroi jea vorvim zoddlolea panancher vo panam pasun bodol  distolo. (Eka vorgonnache vangddi pollovnk, boroi {{ns:category}}:Vorgonnachem nanv). [[Special:Watchlist|Tujea sadurvollerint]] aslolea panacher bodol <strong>datt</strong> asat.",
        "recentchangeslinked-page": "Panache nanv:",
        "recentchangeslinked-to": "Dil'em panache bodlek haka zodlelem panank kel'le bodol dakhoi",
        "upload": "Fayl upload kor",
        "longpages": "Lamb panam",
        "protectedpages-filters": "Challnneo:",
        "listusers": "Vaporpeanchi volleri",
-       "usercreated": "$3 hannem $1 disa $2 vaztam rochlelem",
+       "usercreated": "$3 hannem $1 disa $2 vorancher rochlolem",
        "newpages": "Novim panam",
        "move": "Zago bodol",
        "pager-newer-n": "{{PLURAL:$1|novem 1|novim $1}}",
        "specialloguserlabel": "Korpi:",
        "speciallogtitlelabel": "Mokh (mathallo vo {{ns:user}}:vapurpeachem nanv):",
        "log": "Sotram",
-       "all-logs-page": "Soglle bhousache sotram",
-       "alllogstext": "{{SITENAME}} hacheo sogllea uplobdh sotranchi ektthaim dakhovnni.\nTujean tuzo dekhavo ornum ieta ek sotracho prokar vinchun, vaporpeachem nanv (vhodle and dhakte okxora modem forok podta), vo porinnam zalolem pan (hanga-ui vhodle and dhakte okxora modem forok podta).‎",
+       "all-logs-page": "Sogllim bhousachim sotram",
+       "alllogstext": "{{SITENAME}} hacheo sogllea uplobdh sotranchi ektthaim dakhovnni.\nTujean tuzo dekhavo ornum ieta ek sotracho prokar vinchun, vaporpeachem nanv (vhoddlea ani dhakttea okxora modem forok poddtta), vo porinnam zalolem pan (hangai vhoddle and dhaktte okxora modem forok poddtta).",
        "logempty": "Sotran zullpi nog nant.‎",
        "allpages": "Sogllim panam",
        "nextpage": "Fuddlem pan ($1)",
        "allpagesfrom": "Hanga thavn suru zatelea panank dakhoi:",
        "allarticles": "Sogllim panam",
        "allpagessubmit": "Voch",
-       "allpages-hide-redirects": "Punornirdexonam lipoi",
+       "allpages-hide-redirects": "Punornirdexona lipoi",
        "categories": "Vorg",
        "sp-deletedcontributions-contribs": "iogdan",
        "linksearch-ns": "Nanv-tholl:",
        "watch": "Nodor dovor",
        "watchthispage": "Hea panar dixtt dovor",
        "unwatch": "Nodor kadd",
-       "watchlist-details": "Tujea Sadurvollerint {{PLURAL:$1|$1 pan asa|$1 panam asat}} (te-bhair ulovpachim panam asat).",
+       "watchlist-details": "Tujea Sadurvollerint {{PLURAL:$1|$1 pan asa|$1 panam asat}} (tea-bhair ulovpachim panam asat).",
        "wlheader-showupdated": "Tujea fatle bhette san bodol'lean tim panam '''datt''' dakhoileant.",
-       "wlnote": "Sokoil {{PLURAL:$1|ho nimanno bodol|hem nimanneo <strong>$1</strong> bodol}} nimannea {{PLURAL:$2|horan|<strong>$2</strong> horanim}}, $3, $4 porian.‎",
+       "wlnote": "Sokoil {{PLURAL:$1|ho nimanno bodol|hem nimanneo <strong>$1</strong> bodol}} nimannea {{PLURAL:$2|voran|<strong>$2</strong> voramni}}, $3, $4 porian.‎",
        "watchlist-options": "Sadurvollericheo poryay",
        "watching": "Disht dovortanv...",
        "unwatching": "Disht kaddthanv...",
-       "enotif_reset": "Panam bhett dilolim mhunn khunnai",
+       "enotif_reset": "Sogllim panam bhett dilolim mhunn khunnai",
        "delete-legend": "Kadun udoi",
        "actioncomplete": "Karvai sompurnn",
        "actionfailed": "Karvai oiesiesvi",
        "linkshere": "Sokoilim panam <strong>$2</strong> ak zoddtat:",
        "nolinkshere": "Khoincheim pan <strong>$2</strong> ak zoddna.",
        "isredirect": "punornirdexon pan",
-       "istemplate": "Durasth-somaves",
+       "istemplate": "durasth-somaves",
        "isimage": "faylicho duvo",
        "whatlinkshere-prev": "{{PLURAL:$1|adlem|adlem $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|fuddlem|fuddlim $1}}",
        "whatlinkshere-links": "← duve",
-       "whatlinkshere-hideredirs": "$1 punornirdexonam",
-       "whatlinkshere-hidetrans": "$1 durasth-somaveso",
+       "whatlinkshere-hideredirs": "$1 punornirdexona",
+       "whatlinkshere-hidetrans": "$1 durasth-somavex",
        "whatlinkshere-hidelinks": "$1 duve",
        "whatlinkshere-hideimages": "$1 faylinche duve",
        "whatlinkshere-filters": "Challnio",
        "contribslink": "yogdan",
        "blocklogpage": "addavnnechem sotr",
        "blocklogentry": "[[$1]] addailelem $2 asun vellacho ont: $3",
-       "reblock-logentry": "addavpachem bosovp bodol’lam [[$1]] hache khatir sompacho vell dilam $2 $3‎",
+       "reblock-logentry": "addavpachem bosovp bodol’lam [[$1]] hache khatir sompovpacho vell dila $2 $3‎",
        "block-log-flags-nocreate": "Khatem rochop opatr kelam",
        "proxyblocker": "Protinidhi-sirvidor addavpi‎",
        "move-page": "$1 haloi",
        "allmessagesdefault": "Default sondex mozkur",
        "thumbnail-more": "Vhodlem kor",
        "thumbnail_error": "Lhan-imaz toiar kortana chuk zali. Karonn: $1",
-       "importlogpage": "Aiatacho sotr",
+       "importlogpage": "Aiatachem sotr",
        "tooltip-pt-userpage": "{{GENDER:|Tujem vaporpeachem}} pan",
        "tooltip-pt-mytalk": "{{GENDER:|Tumchem}} bhasabhasachem pan",
        "tooltip-pt-preferences": "{{GENDER:|Tumcheo}} avddi",
        "tooltip-ca-nstab-special": "Hem ek kherit pan, ani hem bodlunk zaina",
        "tooltip-ca-nstab-project": "Prokolpachem pan polloi",
        "tooltip-ca-nstab-image": "Faylichem pan polloi",
-       "tooltip-ca-nstab-mediawiki": "Iontronacho sondex polloi",
+       "tooltip-ca-nstab-mediawiki": "Iontronnacho sondex polloi",
        "tooltip-ca-nstab-template": "Sancho polloi",
        "tooltip-ca-nstab-category": "Vorgachem pan polloi",
        "tooltip-minoredit": "Haka ek kirkoll sudharop mhunn khunnay",
        "tooltip-rollback": "\"Kovllop\" hea panak nimannea yogdan korpean kello (kelle) bodol eka kollant portota.",
        "tooltip-undo": "\"Rodd' kor\" sudharop portita ani sudharopak Zholok ritin ukodta. Tem saran karon zoddunk dita.",
        "tooltip-summary": "Mottvo sar ghal",
-       "simpleantispam-label": "Spam-virudh topasni.\nHem bhori <strong>nakai</strong>!",
+       "simpleantispam-label": "Spam-virudh topasnni.\nHem bhor <strong>nakai</strong>!",
        "pageinfo-title": "\"$1\" ‎khatir mhaiti",
        "pageinfo-header-basic": "Mull mhaiti‎",
        "pageinfo-header-edits": "Bodolacho itihas",
        "pageinfo-header-properties": "Panache gunndhorm",
        "pageinfo-display-title": "Manddlolem mathallem",
        "pageinfo-default-sort": "Default arin manddunk chavi",
-       "pageinfo-length": "Panachi lambai (bayt-ant)‎",
+       "pageinfo-length": "Panachi lambai (baytt-ant)‎",
        "pageinfo-article-id": "Panacho ank",
        "pageinfo-language": "Panachea mozkurachi bhas",
        "pageinfo-content-model": "Panachea mozkuracho nomuno",
-       "pageinfo-robot-policy": "Robotam koddlean suchien ghalop",
+       "pageinfo-robot-policy": "Robotam koddlean suchent ghalop",
        "pageinfo-robot-index": "Porvangi asa",
        "pageinfo-robot-noindex": "Porvangi nam",
        "pageinfo-watchers": "Panacher dixtt dovortoleancho ankddo",
        "pageinfo-toolboxlink": "Panachi mahiti",
        "pageinfo-contentpage": "Ek mozkurachem pan koxem dhorpant ailam‎",
        "pageinfo-contentpage-yes": "Hoi",
-       "patrol-log-page": "Paro korpeacho sotr",
+       "patrol-log-page": "Paro korpachem sotr",
        "previousdiff": "←  Adlo bodol",
        "nextdiff": "Fuddlem bodol →",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|pan|panam}}",
        "redirect": "Fayl, vaporpi, pan, uzollnni vo sotr ank‎ vorvim punornirdexon kor",
        "redirect-summary": "Hem vixex pan punornirdexit korta eka faylik (faylichem nanv dilear), eke panak (uziollnecho ank vo panacho ank dilear), ek vaporpeachem panak (eke vaporpeache ank dilear), vo ek sotr nond (sotrachem ank dilear). Vapor: [[{{#Special:Redirect}}/file/Dekhik.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], vo [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Voch‎",
-       "redirect-lookup": "Suchien polloi:",
+       "redirect-lookup": "Suchint polloi:",
        "redirect-value": "Mol:",
-       "redirect-user": "Vaporpeacho ank",
+       "redirect-user": "Vaporpeancho ank",
        "redirect-page": "Panacho ank",
        "redirect-revision": "Panachi uzollnni",
        "redirect-file": "Faylichem nanv",
        "htmlform-title-not-exists": "$1 ostitvant na.",
        "logentry-delete-delete": "$1, hannem {{GENDER:$2|kadun udoile}} pan $3",
        "logentry-delete-restore": "$1 hannem {{GENDER:$2|porot haddlam}} pan $3 ($4)‎",
-       "logentry-delete-revision": "$1 hannem {{PLURAL:$5|uzolliechem}} disnem  $3, hea panar {{GENDER:$2|bodol’la}}: $4‎",
+       "logentry-delete-revision": "$1 hannem {{PLURAL:$5|uzollnnechem}} disnnem $3, hea panar {{GENDER:$2|bodol’la}}: $4‎",
        "revdelete-content-hid": "mozkur lipoila",
        "logentry-move-move": "$1, hannem $3 panak $4 {{GENDER:$2|haloilea}}",
        "logentry-move-move-noredirect": "$1, hannem pan $3 savn $4 {{GENDER:$2|haloilam}} punornirdexon dorinastanam‎",
        "logentry-move-move_redir": "$1 hannem pan $3 savn $4 {{GENDER:$2|haloilolo}} punornirdexonavoir",
-       "logentry-patrol-patrol-auto": "$1-an $3, hea panachem $4, hea uzollniecho paro kelam mhonn apoap {{GENDER:$2|khunnailam}}.",
+       "logentry-patrol-patrol-auto": "$1-an $3, hea panachem $4, hea uzollnnecho paro kelam mhonn apoap {{GENDER:$2|khunnailam}}.",
        "logentry-newusers-create": "Vapurpeacho kont $1 {{GENDER:$2|rochlam}}",
        "logentry-newusers-autocreate": "Vaporpeachem khatem $1 apoap {{GENDER:$2|rochun}} ailem",
        "logentry-upload-upload": "$1-an $3 {{GENDER:$2|upload kela}}",
index e4efdb8..6480a93 100644 (file)
        "backend-fail-batchsize": "למאגר אחסון הקבצים הפנימי הועבר אוסף של {{PLURAL:$1|פעולת קובץ אחת|$1 פעולות קובץ}}; המגבלה היא {{PLURAL:$2|פעולה אחת|$2 פעולות}}.",
        "backend-fail-usable": "קריאת או כתיבת הקובץ \"$1\" לא הצליחה כיוון שההרשאות אינן מספיקות או כיוון שהספריות/המכלים חסרים.",
        "backend-fail-stat": "לא היה אפשר לקרוא את המצב של הקובץ \"$1\".",
+       "backend-fail-hash": "לא היה אפשר להחליט מהו גיבוב ההצפנה של הקובץ \"$1\".",
        "filejournal-fail-dbconnect": "לא ניתן היה להתחבר לבסיס הנתונים של היומן עבור מאגר אחסון הקבצים הפנימי \"$1\".",
        "filejournal-fail-dbquery": "לא ניתן היה לעדכן את בסיס הנתונים של היומן עבור מאגר אחסון הקבצים הפנימי \"$1\".",
        "lockmanager-notlocked": "פתיחת הנעילה של \"$1\" לא הצליחה; הוא לא נעול.",
index 94945ff..d04622a 100644 (file)
        "diff-multi-manyusers": "({{PLURAL:$1|Nije prikazana jedna međuinačica|Nisu prikazane $1 međuinačice|Nije prikazano $1 međuinačica}} više od {{PLURAL:$2|jednog|$2|$2}} suradnika)",
        "difference-missing-revision": "{{PLURAL:$2|Uređivanje|$2 uređivanja}} sljedeće šifre ($1) ne {{PLURAL:$2|postoji|postoje}}.\n\nOvo je obično uzrokovano kada kliknete na zastarjelu poveznicu na stranice koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} evidenciji brisanja].",
        "searchresults": "Rezultati pretrage",
+       "search-filter-title-prefix": "Pretraži samo stranice koje počinju prefiksom »$1«",
        "searchresults-title": "Rezultati pretrage za \"$1\"",
        "titlematches": "Pronađene stranice prema naslovu",
        "textmatches": "Pronađene stranice prema tekstu članka",
index ba8806b..37106e5 100644 (file)
        "nocreate-loggedin": "Nincs jogosultságod új lapokat létrehozni.",
        "sectioneditnotsupported-title": "A szakaszszerkesztés nem támogatott",
        "sectioneditnotsupported-text": "Ezen a lapon nem támogatott a szakaszok szerkesztése",
+       "modeleditnotsupported-title": "A szerkesztés nem támogatott",
+       "modeleditnotsupported-text": "A következő tartalommodell szerkesztése nem támogatott: $1.",
        "permissionserrors": "Engedélyezési hiba",
        "permissionserrorstext": "A művelet elvégzése nem engedélyezett a számodra, a következő {{PLURAL:$1|ok|okok}} miatt:",
        "permissionserrorstext-withaction": "Nincs jogosultságod a következő művelet elvégzéséhez: $2, a következő {{PLURAL:$1|ok|okok}} miatt:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Üres objektum",
        "content-json-empty-array": "Üres tömb",
+       "unsupported-content-model": "<strong>Figyelem:</strong> A következő tartalommodell nem támogatott ezen a wikin: $1.",
        "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.",
        "undo-norev": "A szerkesztés nem állítható vissza, mert nem létezik vagy törölve lett.",
        "undo-nochange": "A szerkesztés már vissza lett állítva.",
        "undo-summary": "Visszavontam [[Special:Contributions/$2|$2]] ([[User talk:$2|vita]]) szerkesztését (oldid: $1)",
+       "undo-summary-anon": "Visszavontam [[Special:Contributions/$2|$2]] 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.\n\n$3 a következő indoklást adta: <em>$2</em>",
        "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-anon": "Visszaállítottam a lap korábbi változatát [[Special:Contributions/$2|$2]] 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": "{{GENDER:$3|$1}} szerkesztéseit visszaállítottam {{GENDER:$4|$2}} utolsó változatára.",
        "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, küldd el újra az űrlapot.",
        "changecontentmodel": "A lap tartalommodelljének megváltoztatása",
        "changecontentmodel-legend": "Tartalommodell megváltoztatása",
-       "changecontentmodel-title-label": "Lapcím",
+       "changecontentmodel-title-label": "Lapcím:",
        "changecontentmodel-current-label": "Jelenlegi tartalommodell:",
-       "changecontentmodel-model-label": "Új tartalommodell",
+       "changecontentmodel-model-label": "Új tartalommodell:",
        "changecontentmodel-reason-label": "Indoklás:",
        "changecontentmodel-submit": "Változtatás",
        "changecontentmodel-success-title": "A tartalommodell megváltozott",
        "move-subpages": "Allapok átnevezése (maximum $1)",
        "move-talk-subpages": "A vitalap allapjainak átnevezése (maximum $1)",
        "movepage-page-exists": "A(z) „$1” nevű lap már létezik, és nem írható felül automatikusan.",
+       "movepage-source-doesnt-exist": "A(z) „$1” oldal nem létezik, ezért nem lehet átnevezni.",
        "movepage-page-moved": "A(z) „$1” nevű lap át lett nevezve „$2” névre.",
        "movepage-page-unmoved": "A(z) „$1” nevű lap nem nevezhető át „$2” névre.",
        "movepage-max-pages": "{{PLURAL:$1|Egy|$1}} lapnál több nem nevezhető át automatikusan, így a további lapok a helyükön maradnak.",
        "immobile-target-namespace-iw": "Wikiközi hivatkozás nem lehet a lap új neve.",
        "immobile-source-page": "Ez a lap nem nevezhető át.",
        "immobile-target-page": "A lap nem helyezhető át a megadott címre.",
+       "movepage-invalid-target-title": "A célnév érvénytelen.",
        "bad-target-model": "A kívánt célhely eltérő tartalom modellt használ. Nem lehet $1 modellről $2 modellre konvertálni.",
        "imagenocrossnamespace": "A fájlok nem helyezhetőek át más névtérbe",
        "nonfile-cannot-move-to-file": "Nem fájlok nem nevezhetők át fájlnévtérbe",
index 1f35ef6..eb94cd0 100644 (file)
        "undo-main-slot-only": "Le modification non poteva esser disfacite perque illo implica contento foras del cannellatura principal.",
        "undo-norev": "Impossibile annullar le modification proque illo non existe o esseva delite.",
        "undo-nochange": "Pare que iste modification ha jam essite disfacite.",
-       "undo-summary": "Annullava le version $1 per [[Special:Contributions/$2|$2]] ([[User talk:$2|Discussion]] | [[Special:Contributions/$2|{{MediaWiki:Contribslink}}]])",
+       "undo-summary": "Disfacite le version $1 per [[Special:Contributions/$2|$2]] ([[User talk:$2|discussion]])",
+       "undo-summary-anon": "Disfacite le version $1 per [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Disfacer le revision $1 facite per un usator celate",
        "cantcreateaccount-text": "Le creation de contos desde iste adresse IP ('''$1''') ha essite blocate per [[User:$3|$3]].\n\nLe motivo que $3 dava es ''$2''",
        "cantcreateaccount-range-text": "Le creation de contos ab le adresses IP in le intervallo <strong>$1</strong>, le qual include tu adresse IP (<strong>$4</strong>), ha essite blocate per [[User:$3|$3]].\n\nLe motivo fornite per $3 es <em>$2</em>",
        "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>.",
-       "revertpage": "Reverteva modificationes per [[Special:Contributions/$2|$2]] ([[User talk:$2|Discussion]]) al ultime version per [[User:$1|$1]]",
+       "revertpage": "Reverteva modificationes per [[Special:Contributions/$2|$2]] ([[User talk:$2|discussion]]) al ultime version per [[User:$1|$1]]",
+       "revertpage-anon": "Reverteva modificationes per [[Special:Contributions/$2|$2]] al ultime version per [[User:$1|$1]]",
        "revertpage-nouser": "Reverteva modificationes per un usator celate al ultime version per {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Revocava modificationes per {{GENDER:$3|$1}};\nretornava al version per {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Error de session",
index d394695..0ba4725 100644 (file)
        "nocreate-loggedin": "Anda tak memiliki hak akses untuk membuat halaman baru.",
        "sectioneditnotsupported-title": "Penyuntingan bagian tidak didukung",
        "sectioneditnotsupported-text": "Penyuntingan bagian tidak didukung di halaman sunting ini.",
+       "modeleditnotsupported-title": "Penyuntingan tidak didukung",
+       "modeleditnotsupported-text": "Penyuntingan tidak didukung untuk model konten $1.",
        "permissionserrors": "Kesalahan Hak Akses",
        "permissionserrorstext": "Anda tak memiliki hak untuk melakukan hal itu karena {{PLURAL:$1|alasan|alasan-alasan}} berikut:",
        "permissionserrorstext-withaction": "Anda tidak memiliki hak akses untuk $2, karena {{PLURAL:$1|alasan|alasan}} berikut:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Objek kosong",
        "content-json-empty-array": "Larik kosong",
+       "unsupported-content-model": "<strong> Peringatan: </strong> Model konten $1 tidak didukung di wiki ini.",
+       "unsupported-content-diff": "Beda tidak didukung untuk model konten $1.",
+       "unsupported-content-diff2": "Perbedaan antara model konten $1 dan $2 tidak didukung di wiki ini.",
        "deprecated-self-close-category": "Halaman yang menggunakan tag HTML tertutup-sendiri tidak sah",
        "deprecated-self-close-category-desc": "Halaman ini mengandung tag HTML tertutup-sendiri yang tidak sah, seperti <code>&lt;b/></code> atau <code>&lt;span/></code>.  Perilaku tag seperti ini akan segera berubah agar konsisten dengan spesifikasi HTML5, jadi penggunaannya dalam teks wiki tidak lagi disarankan.",
        "duplicate-args-warning": "<strong>Peringatan:</strong> [[:$1]] memanggil [[:$2]] dengan nilai lebih dari satu untuk parameter \"$3\". Hanya nilai terakhir yang tersedia yang akan digunakan.",
        "backend-fail-contenttype": "Tidak dapat menentukan tipe konten dari berkas yang disimpan di \"$1\".",
        "backend-fail-batchsize": "Penyimpanan backend diberikan batch $1 berkas {{PLURAL:$1||}}operasi; batasnya adalah $2 {{PLURAL:$2||}}operasi.",
        "backend-fail-usable": "Tidak dapat membaca atau menulis berkas \"$1\" karena izin tidak memadai atau direktori/kontainer hilang.",
+       "backend-fail-stat": "Tidak dapat membaca status berkas \"$1\".",
+       "backend-fail-hash": "Tidak dapat menentukan hash kriptografi berkas \"$1\".",
        "filejournal-fail-dbconnect": "Tidak dapat menyambung ke database jurnal untuk penyimpanan backend \"$1\".",
        "filejournal-fail-dbquery": "Tidak bisa update database jurnal untuk penyimpanan backend \"$1\".",
        "lockmanager-notlocked": "Tidak bisa membuka kunci \"$1\" karena \"$1\" tidak terkunci.",
        "sessionfailure": "Sepertinya ada masalah dengan sesi log Anda; log Anda telah dibatalkan sebagai antisipasi untuk mencegah pembajakan. Silakan tekan tombol \"kembali\" dan muat kembali halaman sebelum Anda masuk, lalu coba lagi.",
        "changecontentmodel": "Ubah model isi sebuah halaman",
        "changecontentmodel-legend": "Ubah model isi",
-       "changecontentmodel-title-label": "Judul halaman",
+       "changecontentmodel-title-label": "Judul halaman:",
        "changecontentmodel-current-label": "Model konten saat ini:",
-       "changecontentmodel-model-label": "Model konten baru",
+       "changecontentmodel-model-label": "Model konten baru:",
        "changecontentmodel-reason-label": "Alasan:",
        "changecontentmodel-submit": "Ubah",
        "changecontentmodel-success-title": "Model konten ini telah diubah",
        "move-page-legend": "Pindahkan halaman",
        "movepagetext": "Menggunakan formulir di bawah ini akan mengubah nama suatu halaman dan memindahkan semua data sejarah ke nama baru.\nJudul lama akan menjadi halaman pengalihan ke judul baru.\nAnda dapat memperbarui pengalihan yang menuju ke judul asli secara otomatis.\nJika Anda memilih tidak, pastikan untuk memeriksa\n[[Special:DoubleRedirects|pengalihan ganda]] atau [[Special:BrokenRedirects|pengalihan rusak]].\nAnda bertanggung jawab untuk memastikan bahwa pranala terhubung ke tempat seharusnya.\n\nPerhatikan bahwa halaman '''tidak''' akan dipindah apabila telah ada halaman pada judul yang baru, kecuali bila halaman peralihan dan tidak mempunyai sejarah penyuntingan. \nIni berarti Anda dapat mengubah kembali nama halaman seperti semula apabila Anda membuat kesalahan, dan Anda tidak dapat menimpa halaman yang telah ada.\n\n'''Catatan:'''\nIni dapat mengakibatkan perubahan drastis dan tak terduga bagi halaman yang populer; pastikan Anda mengerti konsekuensinya sebelum melanjutkan.",
        "movepagetext-noredirectfixer": "Formulir di bawah ini digunakan untuk mengubah nama suatu halaman dan memindahkan semua data sejarah ke nama baru.\nJudul yang lama akan menjadi halaman peralihan menuju judul yang baru.\nPastikan untuk memeriksa pengalihan [[Special:DoubleRedirects|ganda]] atau [[Special:BrokenRedirects|rusak]].\nAnda bertanggung jawab untuk memastikan bahwa pranala terus menyambung ke halaman yang seharusnya.\n\nPerhatikan bahwa halaman '''tidak''' akan dipindah apabila telah ada halaman yang menggunakan judul yang baru, kecuali bila halaman tersebut kosong atau merupakan halaman peralihan dan tidak mempunyai sejarah penyuntingan.\nIni berarti Anda dapat mengubah nama halaman kembali seperti semula apabila Anda membuat kesalahan, dan Anda tidak dapat menimpa halaman yang telah ada.\n\n'''Catatan:'''\nHal ini dapat mengakibatkan perubahan yang tak terduga dan drastis bagi halaman yang populer;\nPastikan Anda mengerti konsekuensi dari perbuatan ini sebelum melanjutkan.",
+       "movepagetext-noredirectsupport": "Menggunakan formulir di bawah ini akan mengubah nama halaman, memindahkan semua riwayatnya ke nama baru.\nAnda bertanggung jawab untuk memastikan bahwa pranala akan mengarah kemana seharusnya.\n\nPerhatikan bahwa halaman tersebut <strong> tidak </strong> akan dipindahkan jika sudah ada halaman di judul baru.\nIni berarti bahwa Anda dapat mengganti nama halaman kembali ke tempat namanya diubah jika Anda melakukan kesalahan, dan Anda tidak dapat menimpa halaman yang sudah ada.\n\n<strong> Catatan: </strong>\nIni bisa menjadi perubahan drastis dan tidak terduga untuk halaman populer;\npastikan Anda memahami konsekuensinya sebelum melanjutkan.",
        "movepagetalktext": "Jika Anda mencentang kotak ini, halaman pembicaraan berkaitan akan dipindahkan secara otomatis ke judul baru, kecuali halaman pembicaraan tersebut tidak kosong.\n\nDalam kasus tersebut, Anda harus memindahkan atau menggabungkan halaman secara manual.",
        "moveuserpage-warning": "'''Peringatan:''' Anda tengah memindahkan halaman pengguna. Perlu diketahui bahwa hanya halaman yang akan dipindahkan namun pengguna ''tidak akan'' berganti nama.",
        "movecategorypage-warning": "<strong>Peringatan:</strong> Anda akan memindahkan halaman kategori. Perlu diketahui bahwa hanya halaman yang akan dipindahkan dan setiap halaman dalam kategori lama <em>tidak</em> akan dikategorikan ulang ke yang baru.",
        "delete_and_move_reason": "Dihapus untuk mengantisipasikan pemindahan halaman dari \"[[$1]]\"",
        "selfmove": "Tidak dapat memindahkan halaman, karena judul sumber dan judul tujuan sama.",
        "immobile-source-namespace": "Tidak dapat memindahkan halaman dalam ruang nama \"$1\"",
+       "immobile-source-namespace-iw": "Halaman pada wiki lain tidak dapat dipindahkan dari wiki ini.",
        "immobile-target-namespace": "Tidak dapat memindahkan halaman ke ruang nama \"$1\"",
        "immobile-target-namespace-iw": "Pranala interwiki bukanlah target yang valid untuk pemindahan halaman.",
        "immobile-source-page": "Halaman ini tidak dapat dipindahkan.",
        "immobile-target-page": "Tidak dapat memindahkan ke judul tujuan tersebut.",
+       "movepage-invalid-target-title": "Nama yang diminta tidak valid.",
        "bad-target-model": "Tujuan yang diinginkan menggunakan model konten yang berbeda. Tidak dapat mengkonversi dari  $1  untuk  $2 .",
        "imagenocrossnamespace": "Tidak dapat memindahkan berkas ke ruang nama non-berkas",
        "nonfile-cannot-move-to-file": "Tidak dapat memindahkan non-berkas ke ruang nama berkas",
        "common.json": "/* JSON apa pun yang ada di sini akan dimuat untuk semua pengguna ketika memuat semua halaman. */",
        "common.js": "/* JavaScript yang ada di sini akan diterapkan untuk semua kulit. */",
        "group-autoconfirmed.js": "/* Semua JavaScript di sini hanya dimuatkan untuk pengguna terkonfirmasi otomatis */",
+       "group-user.js": "/* Semua Skrip Java di sini hanya dimuatkan untuk pengguna terdaftar saja */",
        "group-bot.js": "/* Semua JavaScript di sini hanya dimuatkan untuk bot */",
        "group-sysop.js": "/* Semua JavaScript di sini hanya dimuatkan untuk pengurus */",
        "group-bureaucrat.js": "/* Semua JavaScript di sini hanya dimuatkan untuk birokrat */",
        "log-name-pagelang": "Log perubahan bahasa",
        "log-description-pagelang": "Ini adalah log perubahan dalam bahasa halaman.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|mengubah}} bahasa $3 dari $4 menjadi $5.",
+       "default-skin-not-found": "Aduh! Kulit baku untuk wiki Anda, didefinisikan dalam <code dir=\"ltr\">$wgDefaultSkin</code> sebagai <code>$1</code>, tidak tersedia.\n\nInstalasi Anda tampaknya menyertakan {{PLURAL:$4|kulit}} berikut ini. Lihat [https://www.mediawiki.org/wiki/Manual:Skin_configuration manual konfigurasi kulit] untuk informasi cara mengaktifkannya {{PLURAL:$4|dan pilih yang baku}}.\n\n$2\n\n; Jika Anda baru saja menginstal MediaWiki:\n: Anda mungkin menginstal dari git, atau langsung dari kode sumber menggunakan beberapa metode. Ini diharapkan. Coba pasang beberapa kulit dari [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org's direktori kulit], dengan:\n: * Mengunduh [https://www.mediawiki.org/wiki/Download tarbal instaler], yang dilengkapi dengan beberapa kulit dan ekstensi. Anda dapat salin tempel direktori <code>kulit/</code> darinya.\n: * Mengunduh tarbal kulit individual dari [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n: * [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Menggunakan Git untuk mengunduh kulit].\n: Melakukan ini seharusnya tidak mengganggu repositori git Anda jika Anda seorang pengembang MediaWiki.\n\n; Jika Anda baru saja upgrade MediaWiki:\n: MediaWiki 1.24 dan yang lebih baru tidak lagi secara otomatis mengaktifkan kulit yang diinstal (lihat [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual kulit autodiscovery]). Anda dapat paste {{PLURAL:$5|baris}} berikut ke <code> LocalSettings.php</code> untuk mengaktifkan {{PLURAL:$5|semua}} {{PLURAL:$5|kulit}} yang terinstal:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Jika Anda baru saja memodifikasi <code>LocalSettings.php</code>:\n: Periksa ulang mana tahi salah ketik.",
+       "default-skin-not-found-no-skins": "Aduh! Kulit baku untuk wiki Anda, didefinisikan dalam <code dir=\"ltr\">$wgDefaultSkin</code> sebagai <code>$1</code>, tidak tersedia.\n\nAnda tidak memiliki skin yang terpasang.\n\n; Jika Anda baru saja menginstal MediaWiki:\n: Anda mungkin menginstal dari git, atau langsung dari kode sumber menggunakan beberapa metode. Ini diharapkan. Coba pasang beberapa kulit dari [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org's direktori kulit], dengan:\n: * Mengunduh [https://www.mediawiki.org/wiki/Download tarbal instaler], yang dilengkapi dengan beberapa kulit dan ekstensi. Anda dapat salin tempel direktori <code>kulit/</code> darinya.\n: * Mengunduh tarbal kulit individual dari [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n: * [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Menggunakan Git untuk mengunduh kulit].\n: Melakukan ini seharusnya tidak mengganggu repositori git Anda jika Anda seorang pengembang MediaWiki. Lihat [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Konfigurasi kulit] untuk informasi cara mengaktifkan kulit dan pilihan baku.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (aktif)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>nonaktif</strong>)",
        "mediastatistics": "Statistik media",
index 81ade2a..28cbba6 100644 (file)
        "diff-empty": "(Nula difero)",
        "diff-multi-sameuser": "(ne montresas {{PLURAL:$1|1 meza revizo|$1 meza revizi}} facita da la sama uzero)",
        "diff-multi-otherusers": "(ne montresas {{PLURAL:$1|1 intermeza revizo|$1 intermeza revizi}} facita da {{PLURAL:$2|altra uzero|$2 altra uzeri}})",
+       "difference-missing-revision": "{{PLURAL:$2|Un revizo|$2 revizi}} de ca difero ($1) ne trovesis.\n\nTo ordinare eventas kande vu sequas obsoleta ligilo a pagino eliminata.\nVu povas vidar detali en la 'log' [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}}, pri efacinda pagini].",
        "searchresults": "Rezultaji dil sercho",
        "search-filter-title-prefix-reset": "Traserchar pagini",
        "searchresults-title": "Sercho-rezultaji por \"$1\"",
        "unusedimages": "Neuzata imaji",
        "wantedcategories": "Dezirata kategorii",
        "wantedpages": "Dezirata pagini",
+       "wantedpages-summary": "Listo pri neexistanta pagini, organizata segun la quanto di ligili a li, ecepte pagini qui nur havas ridirekti a li. Por kompleta listo pri neexistanta pagini, videz la [[{{#special:BrokenRedirects}}|listo pri ruptita ligili]].",
        "wantedfiles": "Dezirata arkivi",
        "wantedtemplates": "Dezirata shabloni",
        "mostcategories": "Pagini maxime kategorizita",
        "fileduplicatesearch-filename": "Nomo dil arkivo:",
        "fileduplicatesearch-submit": "Serchar",
        "specialpages": "Specala pagini",
+       "specialpages-note-top": "Surskriburo",
        "specialpages-group-maintenance": "Raporti pri manteno",
        "specialpages-group-other": "Altra specala pagini",
        "specialpages-group-login": "Enirar / krear konto",
index ad0b23b..ba8f515 100644 (file)
        "nocreate-loggedin": "Non si dispone dei permessi necessari a creare nuove pagine.",
        "sectioneditnotsupported-title": "Modifica delle sezioni non supportata",
        "sectioneditnotsupported-text": "La modifica delle sezioni non è supportata in questa pagina.",
+       "modeleditnotsupported-title": "Modifica non supportata",
+       "modeleditnotsupported-text": "La modifica per il modello di contenuto $1 non è supportata.",
        "permissionserrors": "Permessi non sufficienti",
        "permissionserrorstext": "Non si dispone dei permessi necessari ad eseguire l'azione richiesta, per {{PLURAL:$1|il seguente motivo|i seguenti motivi}}:",
        "permissionserrorstext-withaction": "Non si dispone dei permessi necessari per $2, per {{PLURAL:$1|il seguente motivo|i seguenti motivi}}:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Oggetto vuoto",
        "content-json-empty-array": "Array vuoto",
+       "unsupported-content-model": "<strong>Attenzione:</strong> il modello di contenuto $1 non è supportato in questo wiki.",
+       "unsupported-content-diff": "Le differenze per il modello di contenuto $1 non sono supportate.",
+       "unsupported-content-diff2": "Le differenze fra i modelli di contenuto $1 e $2 non sono supportate su questo wiki.",
        "deprecated-self-close-category": "Pagine che utilizzano tag HTML auto-chiusi non validi",
        "deprecated-self-close-category-desc": "La pagina contiene tag HTML auto-chiusi non validi, come <code>&lt;b/></code> o <code>&lt;span/></code>. Il comportamento di questi presto cambierà per essere coerente con le specifiche HTML5, per questo il loro uso nel wikitesto è deprecato.",
        "duplicate-args-warning": "<strong>Avvertenza:</strong> [[:$1]] chiama [[:$2]] con più di un valore per il parametro \"$3\". Verrà utilizzato solo l'ultimo valore fornito.",
        "action-blockemail": "impedire a un utente di inviare email",
        "action-bot": "essere trattato come processo automatizzato",
        "action-editprotected": "modificare pagine protette con \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "modificare pagine protette con \"{{int:protect-level-autoconfirmed}}\"",
        "action-editinterface": "modificare l'interfaccia utente",
        "action-editusercss": "modificare i file CSS di altri utenti",
        "action-edituserjson": "modificare i file JSON di altri utenti",
        "action-editmyusercss": "modificare i propri file CSS",
        "action-editmyuserjson": "modificare i propri file JSON",
        "action-editmyuserjs": "modificare i propri file JavaScript",
+       "action-editmyuserjsredirect": "modificare i propri file JavaScript che sono reindirizzamenti",
        "action-viewsuppressed": "vedere versioni nascoste a qualsiasi utente",
        "action-hideuser": "bloccare un nome utente, nascondendolo al pubblico",
        "action-ipblock-exempt": "ignorare i blocchi IP, blocchi automatici e blocchi ad intervalli",
+       "action-unblockself": "sbloccare sé stessi",
        "action-noratelimit": "non essere soggetto a limiti di intervallo",
        "action-reupload-own": "sovrascrivere file esistenti caricati da qualcuno",
+       "action-nominornewtalk": "evitare che le modifiche minori a pagine di discussione facciano scattare l'avviso di nuovo messaggio",
+       "action-markbotedits": "segnare le modifiche soggette a rollback come effettuate da bot",
        "action-override-export-depth": "esportare pagine che includono pagine collegate fino ad una profondità di 5",
        "action-suppressredirect": "non creare reindirizzamenti da pagine sorgente quando si spostano le pagine",
        "nchanges": "$1 {{PLURAL:$1|modifica|modifiche}}",
        "blocklist-addressblocks": "Nascondi i blocchi di un solo IP",
        "blocklist-type": "Tipo:",
        "blocklist-type-opt-all": "Tutto",
+       "blocklist-type-opt-sitewide": "Completo",
        "blocklist-type-opt-partial": "Parziale",
        "blocklist-rangeblocks": "Nascondi i blocchi di range",
        "blocklist-timestamp": "Data e ora",
        "move-subpages": "Sposta le sottopagine (sino a $1)",
        "move-talk-subpages": "Sposta le sottopagine di discussione (fino a $1)",
        "movepage-page-exists": "La pagina $1 esiste già e non può essere automaticamente sovrascritta.",
+       "movepage-source-doesnt-exist": "La pagina $1 non esiste e non può essere spostata.",
        "movepage-page-moved": "La pagina $1 è stata spostata a $2.",
        "movepage-page-unmoved": "La pagina $1 non può essere spostata a $2.",
        "movepage-max-pages": "È stato spostato il numero massimo di $1 {{PLURAL:$1|pagina|pagine}} e non potranno essere spostate ulteriori pagine automaticamente.",
        "delete_and_move_reason": "Cancellata per rendere possibile lo spostamento da \"[[$1]]\"",
        "selfmove": "Il titolo è lo stesso, non è possibile spostare una pagina su sé stessa.",
        "immobile-source-namespace": "Non è possibile spostare pagine del namespace \"$1\"",
+       "immobile-source-namespace-iw": "Non è possibile spostare pagine da questo wiki verso altri wiki.",
        "immobile-target-namespace": "Non è possibile spostare pagine nel namespace \"$1\"",
        "immobile-target-namespace-iw": "Un collegamento interwiki non è una destinazione valida per spostare la pagina.",
        "immobile-source-page": "Questa pagina non può essere spostata.",
        "immobile-target-page": "Non è possibile spostare sul titolo indicato.",
+       "movepage-invalid-target-title": "Il titolo richiesto non è valido.",
        "bad-target-model": "La destinazione desiderata utilizza un modello di contenuti diverso. Non è possibile convertire da $1 a $2.",
        "imagenocrossnamespace": "Non è possibile spostare un file fuori dal relativo namespace.",
        "nonfile-cannot-move-to-file": "Non è possibile spostare un file fuori dal relativo namespace.",
        "permanentlink": "Link permanente",
        "permanentlink-revid": "ID versione",
        "permanentlink-submit": "Vai alla versione",
+       "newsection": "Nuova sezione",
+       "newsection-page": "Pagina di destinazione",
+       "newsection-submit": "Vai alla pagina",
        "dberr-problems": "Questo sito sta avendo dei problemi tecnici.",
        "dberr-again": "Prova ad attendere qualche minuto e ricaricare.",
        "dberr-info": "(Impossibile accedere al server del database: $1)",
        "mw-widgets-abandonedit-keep": "Continuare a modificare",
        "mw-widgets-abandonedit-title": "Sei sicuro?",
        "mw-widgets-copytextlayout-copy": "Copia",
+       "mw-widgets-copytextlayout-copy-fail": "Copia negli appunti non riuscita.",
+       "mw-widgets-copytextlayout-copy-success": "Copiato negli appunti.",
        "mw-widgets-dateinput-no-date": "Nessuna data selezionata",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-GG",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
        "gotointerwiki-external": "Stai per lasciare {{SITENAME}} per visitare [[$2]], che è un sito web diverso.\n\n'''[$1 Continua su $1]'''",
        "undelete-cantedit": "Non puoi ripristinare questa pagina poiché non hai sufficienti permessi per modificarla.",
        "undelete-cantcreate": "Non puoi ripristinare questa pagina poiché la pagina con questo nome non è ancora inesistente e non hai sufficienti permessi per crearla.",
+       "pagedata-title": "Dati della pagina",
        "pagedata-not-acceptable": "Nessun formato corrispondente trovato. Tipi MIME supportati: $1",
        "pagedata-bad-title": "Titolo non valido: $1.",
        "unregistered-user-config": "Per motivi di sicurezza, non è possibile caricare sottopagine utente JavaScript, CSS e JSON per utenti non registrati.",
+       "passwordpolicies": "Politiche sulle password",
        "passwordpolicies-summary": "Questo è un elenco delle politiche sulle password efficaci per i gruppi di utenti definiti in questo wiki.",
        "passwordpolicies-group": "Gruppo",
        "passwordpolicies-policies": "Politiche",
        "passwordpolicies-policy-maximalpasswordlength": "La password deve essere lunga meno di $1 {{PLURAL:$1|carattere|caratteri}}",
        "passwordpolicies-policy-passwordcannotbepopular": "La password non può essere {{PLURAL:$1|la password più popolare|nell'elenco delle $1 password più popolari}}",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "La password non può essere nell'elenco delle 100 000 password utilizzate più comunemente.",
+       "passwordpolicies-policyflag-forcechange": "(deve essere modificata all'accesso)",
+       "passwordpolicies-policyflag-suggestchangeonlogin": "(suggerita modifica all'accesso)",
+       "mycustomjsredirectprotected": "Non si dispone dei permessi necessari a modificare questa pagina JavaScript perché si tratta di un redirect che non punta al proprio spazio utente.",
        "easydeflate-invaliddeflate": "Il contenuto fornito non è compresso correttamente",
        "unprotected-js": "Per motivi di sicurezza, non è possibile caricare JavaScript da pagine non protette. Crea javascript solo nel namespace MediaWiki o come sottopagina Utente",
        "userlogout-continue": "Vuoi davvero uscire?"
index 8d7fdc1..8bd7bc0 100644 (file)
        "sessionfailure": "ログインのセッションに問題が発生しました。\nセッション乗っ取りを防ぐため、操作を取り消しました。\nフォームを再送信してください。",
        "changecontentmodel": "ページのコンテンツ・モデルの変更",
        "changecontentmodel-legend": "コンテンツモデルを変更",
-       "changecontentmodel-title-label": "ページ名",
+       "changecontentmodel-title-label": "ページ名:",
        "changecontentmodel-current-label": "現在のコンテンツモデル",
-       "changecontentmodel-model-label": "新しい コンテンツ モデル",
+       "changecontentmodel-model-label": "新しい コンテンツ モデル:",
        "changecontentmodel-reason-label": "理由:",
        "changecontentmodel-submit": "変更",
        "changecontentmodel-success-title": "コンテンツ・モデルは変更されました",
index 736037e..7310bd3 100644 (file)
        "morenotlisted": "ھیہ فہرست مکمل نو۔",
        "mypage": "مہ صفحہ",
        "mytalk": "مہ مشقولگی",
-       "anontalk": "بچے لو IP ھیہ",
+       "anontalk": "مشقولگی",
        "navigation": "رہنمائی",
        "and": "&#32;وا",
        "faq": "عام معلومات",
        "searcharticle": "Go/بوغے",
        "history": "تاریخچہ ء صفحہ",
        "history_short": "تاریخچہ",
-       "updatedmarker": "مہ آخری گیکا پت نوغ",
+       "history_small": "تاریخچہ",
+       "updatedmarker": "تہ آخری گیکا پت نوغ",
        "printableversion": "قابل طبع نسخہ",
        "permalink": "مستقل لنک",
        "print": "طباعت",
        "talk": "تبادلۂ خیال",
        "views": "خیالات",
        "toolbox": "ٹول بکس",
+       "tool-link-userrights": "حلقہ ہائے {{GENDER:$1|صارف}}ہ تبدیلی",
+       "tool-link-userrights-readonly": "{{GENDER:$1|صارف}} و گروہان لوڑے",
+       "tool-link-emailuser": "ھیہ {{GENDER:$1|صارف}}وت ای میل/بشلی کغاز انزاوے",
        "imagepage": "ھوٹوو صفحو لوڑے",
        "mediawikipage": "پیغامو صفحہو لوڑے",
        "templatepage": "سانچو صفحہو لوڑے",
        "jumptonavigation": "رہنمائی",
        "jumptosearch": "تلاش",
        "view-pool-error": "معذرت: تمام سرورا موجودہ وختہ اِضافی بوجھ شیر.\nبو زیادہ صارفین موجودہ وختہ ھیہ صفحو لاڑینیان \nبرائے مہربانی! صفحو لوڑیکو بچے دوبارہ کوشش کوریکاری پروشٹی پھوکرو انتظار کورے.\n\n$1",
-       "generic-pool-error": "معذرت: تمام سرورا موجودہ وختہ اِضافی بوجھ شیر.\nبو زیادہ صارفین موجودہ وختہ ھیہ صفحو لاڑینیان \nبرائے مہربانی! صفحو لوڑیکو بچے دوبارہ کوشش کوریکاری پروشٹی پھوکرو انتظار کورے.\n\n$1",
+       "generic-pool-error": "معذرت: تمام سرورا موجودہ وختہ اِضافی بوجھ شیر.\nبو زیادہ صارفین موجودہ وختہ ھیہ صفحو لاڑینیان \nبرائے مہربانی! صفحو لوڑیکو بچے دوبارہ کوشش کوریکاری پروشٹی پھوکرو انتظار کورے\n\n$1",
+       "pool-timeout": "مقفل کوریکو بچے انتظارو مہلت ختم",
+       "pool-queuefull": "قطار لیگی شیر",
        "pool-errorunknown": "نامعلوم خطا",
+       "pool-servererror": "پول اݰماریکو خدمت دستیاب نیکی ($1)۔",
        "poolcounter-usage-error": "استعمالہ خامی: $1",
        "aboutsite": "تعارف {{SITENAME}}",
        "aboutpage": "Project:کھوار ویکیپیڈیو تعارف",
        "pagetitle-view-mainpage": "{{SITENAME}}",
        "retrievedfrom": "‘‘$1’’ نقل کاردو",
        "youhavenewmessages": "تہ بچے ای $1 شیر۔ ($2)",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|آپ کے لیے}} {{PLURAL:$3|کسی دوسرے صارف|$3 صارفین}} کی جانب سے $1 ($2)۔",
+       "youhavenewmessagesmanyusers": "تہ بچے متعدد صارفینان وولٹیاری $1 ($2)۔",
        "newmessageslinkplural": "{{PLURAL:$1|نوغ پیغام|999=نوغ پیغاماتs}}",
        "newmessagesdifflinkplural": "آخری {{PLURAL:$1|تبدیلی|تبدیلی}}",
        "youhavenewmessagesmulti": "ء$1 تہ بچے نوغ نوغ پیغامات شینی",
        "databaseerror-text": "ڈیٹا بیس کیوریا خامی پیدا بیتی شیر.\nھیہ سافٹ ویئراای مسئلہ (بگ)و نشاندیکو بوس.",
        "databaseerror-textcl": "ڈیٹا بیس کیوریا خامی پیدا بیتی شیر.",
        "databaseerror-query": "کیوری: $1",
-       "databaseerror-function": "فنکشن: $ 1",
-       "databaseerror-error": "خرابی: $ 1",
+       "databaseerror-function": "فنکشن:$ 1",
+       "databaseerror-error": "نقص: $1",
        "laggedslavemode": "Warning: Page may not contain recent updates.\nخبردار: منکھن شیر کہ صفحہا موجودہ بتاریخہ جات شامل نو بونی",
        "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\" قسمو تباہ کن استثنا",
        "filecopyerror": "\"$1\" مسلو \"$2\" نقل کورونو نو ھوی",
        "filerenameerror": "مسلو \"$1\" و \"$2\" خور نم دیونو نو ھوی",
        "filedeleteerror": "مسلو \"$1\" حذف کورونو نو ھوی",
        "delete-hook-aborted": "حذف شدگی روکا کورونو ہوئے\nوضاحت کورونو نو ہوئے",
        "badtitle": "خراب عنوان",
        "badtitletext": "'درخاس شدہ صفحہو عنوان ناقص، خالی، یا کیہ غلط ربط شدہ بین لسانی یا بین ویکی عنوان شیر.\nشاید ھیارا ای یا زیات ھݰ حروف موجود شینی کہ ھیت عنوانا استعمال نو بونیان.',",
+       "title-invalid-empty": "درخواست شدہ عنوان خالی شیر یا ھیرا محض نام فضاو نام شیر۔",
+       "title-invalid-utf8": "درخواست شدہ عنوانہ نادرست یونیکوڈ حروف موجود شینی۔",
        "perfcached": "ذیلی ڈیٹا ابطن شدہ شیر وا ھمو بیکا امکان شیر A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
        "perfcachedts": "ذیلی ڈیٹا ابطن شدہ شیر وا آخری بار ھمو بتاریخیت $1 کورونو ہوئے. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.",
        "querypage-no-updates": "ھیہ صفحہو بچے بتاریخات فی الحال ناقابل ساوزینو بیتی شینی. \nھمو ڈیٹا ھنیسے تازہ کورونو نو بوئے",
        "viewsource": "مسودو لوڑے",
        "viewsource-title": "$1 و مسودو لوڑے",
+       "actionthrottled": "Action throttled",
        "actionthrottledtext": "بطورِ ای ضدسپم تدبیر، تہ مختصار وختہ کئی دفعہ ھیہ کورومو کوریکو وجھین محدود کورونو ہوئے، وا تو ھیہ حدو پار کوری آسوس.\nبراہِ کرم، ای کما میلیٹ آچہ کھوشش کورے",
        "protectedpagetext": "ھیہ صفحہو تدویناری محفوظ لاکھیکو بچے قلف لیگینو بیتی شیر",
        "viewsourcetext": "تو صرف مضمونو لوڑیکو بوس وا ھو نقل کوریکو بوس:",
        "translateinterface": "تمام ویکیپیڈا تبدیلی یا شامل کوریکو بچے، ھمو استعمال کورے [https://translatewiki.net/ translatewiki.net]، میڈیا ویکی دارالترجمہ.",
        "namespaceprotected": "\"تتے '''$1''' فضائے نامہ صفحاتن تدوینو کوریکو اِجازت نیکی.\",",
        "mycustomcssprotected": "تے ھیہ سی ایس ایس (CSS) صفحہا ترمیم کوریکو اختیار نیکی۔",
+       "mycustomjsonprotected": "تتے ھیہ سی ایس ایس (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": "غلط عنوان",
+       "invalidtitle-knownnamespace": "«$2» نام فضا «$3» متنو سورا مشتمل عنوان نادرست شیر",
+       "invalidtitle-unknownnamespace": "نامعلوم نام فضا عدد «$1» اوچے «$2» متنو سورا مشتمل عنوان نا درست شیر",
+       "exception-nologin": "لاگ ان نو",
+       "exception-nologin-text": "براہ مہربانی! ھیہ صفحا رسائی یا ترمیمو بچے لاگ ان بوس۔",
        "virus-badscanner": "\"خراب وضعیت: نوژان وائرسی مفراس: ''$1''\",",
        "virus-scanfailed": "تفریس ناکام (رمز $1)",
        "virus-unknownscanner": "نوژان ضد وائرس:",
-       "logouttext": "'''ھنیسے تو خارج بیتی آسوس'''<br />\nتو خفی الاسم {{SITENAME}}  استعمال جاری لاکھیکو بوس، یا دوبارہ ھیہ نامو یا مختلف نامان سورا داخل دی بیکو بوس۔  ھیہ یاد آوری کورے کہ ای کما صفحات ھش <span class='plainlinks'>[$1 دوباری لاگن بوس]</span> غیچھی گونی کہ تو ھنیسے خارج نو بیتی آسوس، کلہ پت کہ تو تان تفصحہ (براؤزرو) ابطن (cache) صاف نوکوروس۔\",",
+       "logouttext": "'''ھنیسے تو خارج بیتی آسوس'''<br />\nتو خفی الاسم {{SITENAME}}  استعمال جاری لاکھیکو بوس، یا دوبارہ ھیہ نامو یا مختلف نامان سورا داخل دی بیکو بوس۔  ھیہ یاد آوری کورے کہ ای کما صفحات ھش <span class='plainlinks'>[$1 دوباری لاگن بوس]</span> غیچھی گونی کہ تو ھنیسے خارج نو بیتی آسوس، کلہ پت کہ تو تان تفصحہ (براؤزرو) ابطن (cache) صاف نوکوروس۔\"",
+       "cannotlogoutnow-title": "ھنیسے خارج بیکو نو بوس",
+       "cannotlogoutnow-text": "$1 و استعمالو موژی خارج بیک ممکن نو۔",
+       "welcomeuser": "خوشان گیور، $1!",
+       "welcomecreation-msg": "تہ کھاتہ کھولاو ہوئے۔\nتو [[Special:Preferences|تان ترجیحات]]ہ حسب منشا تبدیلی کوریکو بوس۔",
        "yourname": "اسمِ رکنیت",
        "userlogin-yourname": "اسمِ رکنیت",
        "userlogin-yourname-ph": "تان صارف نام درج کورے",
        "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": "کھاتہ کھولاو کورے یا اندراج کورے",
        "logout": "لاگ آوٹ",
        "userlogout": "لاگ آوٹ",
        "createacct-reason-ph": "تو ڈبل کھاتہ کھیوتے ساوزیسان؟",
        "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|صفحہ|صفحات}}",
        "wrongpassword": "تو غلط کلمۂ شناخت درج کوری آسوس دوبارہ کو شش کورے",
        "wrongpasswordempty": "کلمۂ شناخت ندارد۔ دوبارہ کوشش کورے",
        "passwordtooshort": "تہ منتخب کردہ پارلوظ(پاسورڈ) مختصار شیر. پارلوظ کم از کم {{PLURAL:$1|1 حرف|$1 حروف}} بیلک.",
+       "passwordtoolong": "تہ منتخب کردہ پارلوظ(پاسورڈ) مختصار شیر. پارلوظ کم از کم {{PLURAL:$1|1 حرف|$1 حروف}} بیلک.",
        "password-name-match": "تہ پارلوظ(پاسورڈ) تہ اسمِ صارفو ساری مختلف بیلک.",
        "mailmypassword": "پاسورڈو ری سیٹ کورے",
        "passwordremindertitle": "نوغ عارضی کلمۂ شناخت برائے {{SITENAME}}",
        "emailconfirmlink": "تان برقی پتو تصدیقو کورے",
        "invalidemailaddress": "تہ بشلی کغاز(ای میل) قبول کورونو نو بویان کوریکو کہ ھیہ غلط شکلا شیر.\nبراہِ کرم! ای برقی پتہ(ای میل ایڈرس) صحیح شکلا درج کورے یا ژاغو خالی پیڅھے.",
        "accountcreated": "تخلیقِ کھاتہ",
-       "accountcreatedtext": "تخیلقِ کھاتۂ ممبار براۓ $1۔",
+       "accountcreatedtext": "[[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|تبادلۂ خیال]]) و صارف کھاتہ ساوز بیتی شیر۔",
        "createaccount-title": "کھاتہ سازی برائے {{SITENAME}}",
        "login-throttled": "تو داخلِ نوشتہ بیکو بچے بو زیادہ کوششیں آرو.\nدوبارہ کوشش کوریکو بچے پھوک مدا انتظار کورے.",
+       "login-abort-generic": "لاگ ان ناکام - منسوخ شد",
        "loginlanguagelabel": "زبان: $1",
        "pt-login": "داخل بوس",
        "pt-login-button": "داخل بوس",
+       "pt-login-continue-button": "داخل بوس",
        "pt-createaccount": "کھاتہ ساوزاوے",
        "pt-userlogout": "لاگ آوٹ",
+       "php-mail-error-unknown": "پی ایچ پیو mail() فنکشنہ نامعلوم نقص۔",
        "changepassword": "پاسورڈو بدیل کورے",
        "resetpass_announce": "تو ای برقی ارسال کردہ عارضی کوڈ ورڈو سوم جستہ داخل بیتی آسوس.\nداخلِ نوشتہ بیکو عملو مکمل کوریکو بچے تہ ھیارا نوغ پاسورڈ متعین کوریلک بوئے جما:",
        "resetpass_header": "کھاتو پاسورڈو تبدیل کورے",
        "newpassword": "نوغ کلمۂ شناخت",
        "retypenew": "نوغ کلمۂ شناخت دوبارہ درج کورے:",
        "resetpass_submit": "پاسورڈ ساوزاوے وا داخل بوس",
+       "botpasswords": "روبہ پاس ورڈ",
+       "botpasswords-disabled": "روبوٹو پاس ورڈ غیر فعال شینی۔",
+       "botpasswords-existing": "روبوٹو موجودہ پاس ورڈ",
+       "botpasswords-createnew": "روبوٹو نوغ پاس ورڈ ساوزاوے",
+       "botpasswords-label-appid": "روبوٹو نام:",
+       "botpasswords-label-create": "ساوزاوے",
+       "botpasswords-label-update": "اپڈیٹ کورے",
+       "botpasswords-label-cancel": "کھینسل",
        "botpasswords-label-delete": "بوغاوے",
        "botpasswords-label-resetpassword": "پاسورڈو ری سیٹ کورے",
+       "botpasswords-label-grants-column": "دیونو ہوئے",
+       "botpasswords-bad-appid": "روبہ نام \"$1\" درست نو۔",
        "resetpass_forbidden": "تتے پاسورڈو چینج کوریکو اجازت نیکی",
        "resetpass-no-info": "ھیہ صفحا براہِ راست رسائیو بچے تہ داخلِ نوشتہ بیک ضروری شیر.",
        "resetpass-submit-loggedin": "پاسورڈو تبدیلی",
        "summary-preview": "نمائش خلاصہ:",
        "subject-preview": "عنوان/شہ سرخیو پیش منظر:",
        "blockedtitle": "صارفو بلاک کورونو بیتی شیر",
+       "blockedtext": "<strong>آپ کے صارف نام یا آئی پی پتہ پر پابندی لگائی جا چکی ہے۔</strong>\n\n$1 نے پابندی عائد کی اور یہ وجہ درج کی: <em>$2</em>\n\n* پابندی کی ابتدا : $8\n* پابندی کا اختتام : $6\n* ممنوع صارف: $7\n\nآپ $1 یا کسی دوسرے [[{{MediaWiki:Grouppage-sysop}}|منتظم]] سے رابطہ کر کے اس پابندی پر گفت و شنید کر سکتے ہیں۔\nواضح رہے کہ آپ «{{int:emailuser}}» کی سہولت اُس وقت تک استعمال نہیں کر سکتے جب تک آپ اپنے [[Special:Preferences|کھاتہ کی ترجیحات]] میں درست برقی ڈاک پتا درج نہ کریں اور آپ کو اِسے استعمال کرنے سے روک نہ دیا گیا ہو۔\nآپ کا موجودہ آئی پی پتہ $3 ہے اور پابندی کا شناختی نمبر #$5 ہے۔\nاگر آپ پابندی سے متعلق کہیں استفسار کریں تو براہِ مہربانی اس میں درج بالا تمام تفصیلات شامل کریں۔",
        "blockednoreason": "کیہ وجہ دیونو نو ھوی",
        "whitelistedittext": "ترمیم کوریکو بچے $1 ضروری شیر.",
        "nosuchsectiontitle": "قطعہ ملاو نو ھوی",
        "accmailtitle": "کلمہ شناخت(پاسورڈ) انځینو ھوی",
        "newarticle": "(نوغ)",
        "newarticletext": "↓تو ای ھݰ صفحو ربطو پیرویو کوری اسوس کہ ھسے ھنیسے موجود نیکی.\nھیہ صفحہو تخلیق کوریکو بچے درج ذیل خانا متنو درج کورے (مزید معلوماتو بچے [$1 صفحۂ معاونت] ملاحظہ کورے).\nاگر تو ھیا غلطیو سورا کہ گیتی اسوس تھے اچھو صفحا آچی بیکو بچے تان کمپیوٹرا '''back''' بٹنو ٹک کورے.",
+       "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}}}} متعلقہ نوشتہ جات تلاش کوریکو بوس],\nیا [{{fullurl:{{FULLPAGENAME}}|action=edit}} تو ھیہ صفحہا ترمیم کوریکو بوس]</span>",
        "userpage-userdoesnotexist-view": "صارف کھاتہ \"$1\" موجود نیکی۔",
+       "clearyourcache": "<strong>یاددہانی:</strong> محفوظ کرنے کے بعد ان تبدیلیوں کو دیکھنے کے لیے آپ کو اپنے براؤزر کا کیش (cache) صاف کرنا ہوگا۔\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) صاف کریں",
        "updated": "(اپ ڈیٹڈ)",
        "note": "'''نوٹ:'''",
        "previewnote": "'''یاد لاکھے، ھیہ صرفی نمائش شیر، تہ کاردو ترامیم ھنیسے محفوظ کورونو نو بیتی شینی۔'''",
        "post-expand-template-inclusion-category": "ھش صفحات کہ ھتیرا ٹمپلیٹ یعنی سانچو ناپ لوٹ بیتی شیر۔",
        "post-expand-template-argument-warning": "'''خبردار:''' ھیہ صفحا ای سانچہ(ٹمپلیٹ) مشقولگی دیونو بیتی شیر وا ھمو سایز بولوٹ شیر۔\nھمی لوان نیزونو بیتی شیر",
        "post-expand-template-argument-category": "ھش صفحات کہ ھتیرا بوغینو بیرو سانچان یعنی(ٹمپلیٹان) لو شینی۔",
+       "undo-failure": "درمیان میں متنازع ترامیم کی موجودگی کی بنا پر اس ترمیم کو واپس نہیں پھیرا جا سکا۔",
        "viewpagelogs": "ھیہ صفحہو بچے نوشتہ جاتن لوڑے",
        "currentrev-asof": "حالیہ نظرثانی بمطابق $1",
        "revisionasof": "تجدید بمطابق $1",
        "revdel-restore": "ظاہریتو تبدیل کورے",
        "mergehistory-reason": "وجہ:",
        "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
+       "mergelog": "نوشتو انضمام",
        "revertmerge": "غیر ضم",
        "history-title": "تاریخچہ \"$1\"",
        "difference-title": "ایڈٹ کاردوان موژی فرق \"$1\"",
        "editundo": "آچی(Undo)",
        "diff-empty": "(کیہ فرق نیکی)",
        "diff-multi-sameuser": "({{PLURAL:$1|One intermediate revision|$1 intermediate revisions}} by the same user not shown)",
+       "diff-multi-otherusers": "({{PLURAL:$2|ایک دوسرے صارف|$2 صارفین}} {{PLURAL:$1|کا ایک درمیانی نسخہ نہیں دکھایا گیا|$1 کے درمیانی نسخے نہیں دکھائے گئے}})",
        "searchresults": "تلاشو نتیجہ",
        "searchresults-title": "نتائجِ تلاش برائے \"$1\"",
        "notextmatches": "ھیہ عنوانو سورا کیہ دی صفحہ موجود نیکی",
        "filehist-comment": "تبصرہ",
        "imagelinks": "مسلو روابط",
        "linkstoimage": "ھیہ مسلو سوم درج ذیل {{PLURAL:$1|صفحہ مربوط شیر|$1 صفحات مربوط شینی}}",
+       "linkstoimage-more": ".",
        "nolinkstoimage": "ھیہ کھوار ویکیپیڈیا ھش کیہ صفحات نیکی کہ ھتیت ھیہ مسل (فائلو) متعلقہ شینی",
        "linkstoimage-redirect": "$1 (فائل ری ڈائرکٹ) $2",
        "sharedupload-desc-here": "ھیہ فائل $1 موژاری شیر وا ھیہ خور پرجیکٹہ استعمال بویان۔\nمزید معلومات ھمو [$2 فائل مشقولگی صفحہا]  دیونو بیتی شیر",
        "speciallogtitlelabel": "ہدف (عنوان یا {{ns:user}}:صارفو نام برائے صارف):",
        "log": "نوشتہ جات",
        "all-logs-page": "تمام عوامی نوشتہ جات",
+       "alllogstext": "{{SITENAME}} کے تمام دستیاب لاگز کا پیوستہ ڈسپلے۔\nآپ اور باریکی کے لیے لاگ کی قسم، صارف نام (حساس معاملہ)، یا متاثرہ صفحہ (یہ بھی حساس معاملہ) چن سکتے ہیں۔",
+       "logempty": "نوشتہ میں اس سے مشابہ کوئی اندراج موجود نہیں ہے۔",
        "allpages": "سف صفحات",
        "prevpage": "آچھو صفحہ ($1)",
        "allpagesfrom": "مطلوبہ حرفاری شروع باک صفحاتن نمائش:",
        "watchthispage": "ھیہ صفحو تان نظرا لاکھے",
        "unwatch": "زیرنظرمنسوخ",
        "watchlist-details": "تہ زیرِنظرفہرستا {{PLURAL:$1|$1 صفحہ شیر|$1 صفحات شینی}}، ھیارا تبادلۂ خیالو صفحاتن تعداد شامل نیکی.",
+       "wlheader-showupdated": "آپ کی آخری آمد کے بعد جن صفحات میں تبدیلی ہوئی ہے وہ <strong>جلی حروف</strong> میں نظر آئیں گے۔",
+       "wlnote": "ذیل میں گزشتہ {{PLURAL:$2|گھنٹے|<strong>$2</strong> گھنٹوں}} میں ہونے والی {{PLURAL:$1|تبدیلی|<strong>$1</strong> تبدیلیوں}} کی فہرست درج ہے، تاریخ تجدید $3، $4",
        "watchlist-options": "واچ لسٹ آپشن",
        "watching": "زیر نظر",
        "unwatching": "منسوخ",
        "mycontris": "مہ حصہ",
        "anoncontribs": "آرٹیکلز",
        "contribsub2": "{{GENDER:$3|$1}} ($2)",
+       "nocontribs": "اس معیار کے مطابق کوئی ترمیم دستیاب نہیں ہوئی۔",
        "uctop": "موجودہ نسخہ",
        "month": "مس (وا ھیغاری پروشٹی):",
        "year": "سال (وا ھیغاری پروشٹی):",
        "contribslink": "حصہ داری",
        "blocklogpage": "نوشتۂ پاوبندی",
        "blocklogentry": "بلاک[[$1]] وختہ پت $2 $3",
+       "reblock-logentry": "[[$1]] کی ترتیبات پابندی کو تبدیل کیا، اب میعاد $2 $3 پر ختم ہوگی",
        "unblocklogentry": "بلاک نو کاردو $1",
        "block-log-flags-nocreate": "کھاتہ کھولاو کوریکو سورا پاوپندی شیر",
        "ipb_expiry_invalid": "Expiry ٹیم غلط شیر.",
        "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-recent-authors": "مختلف مصنفینان حالیہ تعداد",
        "pageinfo-magic-words": "جادوئی {{PLURAL:$1|لوظ|الفاظ}} ($1)",
        "pageinfo-hidden-categories": "کھوشت {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
+       "pageinfo-templates": "زیر استعمال {{PLURAL:$1|سانچہ|سانچے}} ($1)",
        "pageinfo-toolboxlink": "معلومات صفحہ",
+       "pageinfo-contentpage": "شمار بطور صفحہ",
        "pageinfo-contentpage-yes": "دی",
        "patrol-log-page": "پیٹرول لاگ",
        "previousdiff": " ← پرانو تدوین",
        "nextdiff": "صفحہو نم:",
        "widthheightpage": "$1×$2، $3 {{PLURAL:$3|صفحہ|صفحات}}",
        "file-info-size": "$1 × $2 پکسلز, فل سائز: $3, MIME ٹائپ: $4",
+       "file-info-size-pages": "$1 × $2 پکسل، فائل کا حجم: $3، MIME قسم: $4، $5 {{PLURAL:$5|صفحہ|صفحات}}",
        "file-nohires": "ھموغاری لوٹ ریزولیوشن موجود نیکی.",
        "svg-long-desc": "SVG فایل, nominally $1 × $2 پکسلز, فایل سایز: $3",
        "show-big-image": "اصل فائل",
        "watchlisttools-raw": "نوغ واچ لسٹان ایڈیٹ کورے",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|talk]])",
        "duplicate-defaultsort": "'''خبردار:''' ڈیفالٹ تاڑٰ(نغڑی) \"$2\" پروشٹیو ڈیفالٹ تاڑا \"$1\" لیگی شیر۔",
+       "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": "قدر:",
        "compare-page1": "صفحہ 1",
        "logentry-delete-delete": "$1 {{GENDER:$2|حذف کورونو ہوئے}} صفحہ $3",
        "logentry-delete-restore": "$1 صفحو $3 {{GENDER:$2|بحال آریر}}",
+       "logentry-delete-revision": "$1 نے $3 میں موجود {{PLURAL:$5|ایک نسخے|$5 نسخوں}} کی مرئیت کو {{GENDER:$2|تبدیل کیا}}: $4",
        "revdelete-content-hid": "موادو کھوشتئینو بیتی شیر",
        "logentry-move-move": "$1 {{GENDER:$2|moved}} صفحہ $3  پت $4",
        "logentry-move-move-noredirect": "$1 صفحہ $3 و $4 وولٹی ری ڈائرکٹ {{GENDER:$2|آریر}}",
        "logentry-move-move_redir": "$1 ری ڈائرکٹان ہٹاو کوری صفحہ $3 و $4 وولٹی {{GENDER:$2|منتقل آریر}}",
+       "logentry-patrol-patrol-auto": "$1 صفحہ $3 و نسخو $4 خودکار طورا مراجعت شدہ {{GENDER:$2|نشان زد آریر}}",
        "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|ساوزیینو ھوئے}}",
        "logentry-newusers-autocreate": "صارف کھاتہ $1 خودکار طورا {{GENDER:$2|ساوز ہوئے}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|uploaded}} $3",
index 52a713f..769891d 100644 (file)
@@ -77,7 +77,8 @@
                        "Comjun04",
                        "Son77391",
                        "Jango",
-                       "D6283"
+                       "D6283",
+                       "Ktrst"
                ]
        },
        "tog-underline": "링크에 밑줄 긋기:",
        "systemblockedtext": "당신의 사용자 이름 또는 IP 주소가 자동으로 미디어위키에 의해 차단되었습니다.\n이유는 다음과 같습니다:\n\n:<em>$2</em>\n\n* 차단 시작: $8\n* 차단 만료: $6\n* 차단 대상: $7\n\n당신의 현재 IP 주소는 $3입니다.\n문의에 대해 상기의 상세 설명을 모두 포함해 주십시오.",
        "blockednoreason": "이유를 입력하지 않음",
        "blockedtext-composite": "<strong>당신의 사용자 이름 또는 IP 주소가 미디어위키에 의해 차단되었습니다.\n\n이유는 다음과 같습니다:\n\n:<em>$2</em>\n\n* 차단 시작: $8\n* 차단 만료: $6\n\n* $5\n\n당신의 현재 IP 주소는 $3입니다.\n문의에 대해 상기의 상세 설명을 모두 포함해 주십시오.",
+       "blockedtext-composite-ids": "관련 블록 ID: $1 (IP 주소는 블랙리스트에 추가될 수도 있습니다)",
+       "blockedtext-composite-no-ids": "IP 주소가 여러 블랙리스트에 나타납니다",
+       "blockedtext-composite-reason": "당신의 계정 또는 IP 주소가 여러 번 차단되었습니다",
        "whitelistedittext": "문서를 편집하기 전에 $1해야 합니다.",
        "confirmedittext": "문서를 고치려면 이메일 인증 절차가 필요합니다.\n[[Special:Preferences|사용자 환경 설정]]에서 이메일 주소를 입력하고 이메일 주소 인증을 해주시기 바랍니다.",
        "nosuchsectiontitle": "문단을 찾을 수 없음",
        "sectioneditnotsupported-title": "부분 편집이 지원되지 않음",
        "sectioneditnotsupported-text": "이 문서에서는 문단 편집을 지원하지 않습니다.",
        "modeleditnotsupported-title": "편집이 지원되지 않습니다",
+       "modeleditnotsupported-text": "편집은 콘텐츠 모델 $1에서 지원되지 않습니다.",
        "permissionserrors": "권한 오류",
        "permissionserrorstext": "해당 명령을 수행할 권한이 없습니다. 다음 {{PLURAL:$1|이유}}를 확인해보세요:",
        "permissionserrorstext-withaction": "$2 권한이 없습니다. 다음 {{PLURAL:$1|이유}}를 확인해주세요:",
        "content-model-css": "CSS",
        "content-json-empty-object": "빈 오브젝트",
        "content-json-empty-array": "빈 배열",
+       "unsupported-content-model": "<strong>경고:</strong> $1 콘텐츠 모델은 이 위키에서 지원되지 않습니다.",
+       "unsupported-content-diff": "차이 비교는 콘텐츠 모델 $1에서 지원되지 않습니다.",
+       "unsupported-content-diff2": "콘텐츠 모델 $1, $2 간의 차이 비교는 이 위키에서 지원되지 않습니다.",
        "deprecated-self-close-category": "유효하지 않은, 스스로 닫는 HTML 태그를 사용하고 있는 문서",
        "deprecated-self-close-category-desc": "이 문서는 <code>&lt;b/></code>나 <code>&lt;span/></code>와 같은 유효하지 않은, 스스로 닫는 HTML 태그를 포함하고 있습니다. 이 태그들의 동작은 곧 HTML5 사양과 일관되도록 변경될 예정이므로 위키텍스트에서 이것들을 사용하는 것은 권장되지 않습니다.",
        "duplicate-args-warning": "<strong>경고:</strong> [[:$1]] 문서는 [[:$2]]에 \"$3\" 변수를 하나보다 더 많이 입력했습니다. 마지막으로 주어진 값만이 유효합니다.",
        "undo-norev": "문서가 없거나 삭제되었기 때문에 편집을 되돌릴 수 없습니다.",
        "undo-nochange": "편집이 이미 되돌려진 것으로 나타납니다.",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|토론]])의 $1판 편집을 되돌림",
+       "undo-summary-anon": "[[Special:Contributions/$2|$2]]의 $1 판을 되돌림",
        "undo-summary-username-hidden": "숨겨진 사용자가 $1 판을 되돌림",
        "cantcreateaccount-text": "현재 IP 주소('''$1''')는 [[User:$3|$3]] 사용자에 의해 계정 만들기가 차단되었습니다.\n\n차단 이유는 다음과 같습니다: $2",
        "cantcreateaccount-range-text": "당신의 IP 주소(<strong>$4</strong>)가 속해 있는 <strong>$1</strong> 대역에서의 계정 만들기를 [[User:$3|$3]]님이 차단했습니다.\n\n$3님이 제시한 이유는 \"$2\"입니다.",
        "search-interwiki-more": "(더 보기)",
        "search-interwiki-more-results": "더 많은 결과",
        "search-relatedarticle": "관련",
+       "search-invalid-sort-order": "$1의 정렬 순서를 인식하지 못했습니다. 기본 정렬이 적용됩니다. 유효한 정렬 순서는 다음과 같습니다: $2",
+       "search-unknown-profile": "$1의 검색 프로파일을 인식하지 못했습니다. 기본 검색 프로파일이 적용됩니다.",
        "searchrelated": "관련",
        "searchall": "모두",
        "showingresults": "'''$2'''번 부터의 {{PLURAL:$1|결과 '''1'''개|결과 '''$1'''개}}입니다.",
        "right-editmyusercss": "자신의 사용자 CSS 파일 편집하기",
        "right-editmyuserjson": "자신의 사용자 JSON 파일 편집하기",
        "right-editmyuserjs": "자신의 사용자 자바스크립트 파일 편집하기",
+       "right-editmyuserjsredirect": "넘겨주기인 자신의 사용자 자바스크립트 파일 편집하기",
        "right-viewmywatchlist": "자신의 주시문서 목록 보기",
        "right-editmywatchlist": "자신의 주시문서 목록을 편집합니다. 이 권한이 없어도 문서를 추가할 수 있는 권한이 이외에도 있음을 참고하세요.",
        "right-viewmyprivateinfo": "자신의 개인정보 보기 (이메일 주소, 실명 등)",
        "action-editmyusercss": "자신의 사용자 CSS 파일 편집하기",
        "action-editmyuserjson": "자신의 사용자 JSON 파일 편집하기",
        "action-editmyuserjs": "자신의 사용자 자바스크립트 파일 편집하기",
+       "action-editmyuserjsredirect": "넘겨주기인 자신의 사용자 자바스크립트 파일 편집하기",
        "action-viewsuppressed": "어떤 사용자도 보지 못하도록 감춰진 판 보기",
        "action-hideuser": "사용자 이름을 차단하고 감춤",
        "action-ipblock-exempt": "IP 차단, 자동 차단, 광역 차단을 무시",
        "backend-fail-batchsize": "저장 백엔드에서 파일 {{PLURAL:$1|작업}} $1개가 쌓였습니다. 한계는 {{PLURAL:$2|작업}} $2개입니다.",
        "backend-fail-usable": "파일 읽기/쓰기 권한이 없거나 저장 위치가 빠졌기 때문에 \"$1\" 파일을 읽거나 쓸 수 없습니다.",
        "backend-fail-stat": "\"$1\" 파일의 상태를 읽지 못했습니다.",
-       "backend-fail-hash": "\"$1\" 파일의 암호화 해시를 결정하지 못했습니다.",
+       "backend-fail-hash": "\"$1\" 파일의 암호화 해시를 결정하지 못했습니다",
        "filejournal-fail-dbconnect": "저장소 백엔드 \"$1\"에 대한 저널 데이터베이스에 연결할 수 없습니다.",
        "filejournal-fail-dbquery": "저장소 백엔드 \"$1\"에 대한 저널 데이터베이스에서 새로 고칠 수 없습니다.",
        "lockmanager-notlocked": "\"$1\" 경로의 잠금을 풀 수 없습니다. 해당 경로는 잠겨 있지 않습니다.",
        "move": "이동",
        "movethispage": "이 문서 이동하기",
        "unusedimagestext": "다음은 어떠한 문서에서도 사용하지 않는 파일의 목록입니다.\n다른 사이트에서 URL 접근을 통해 파일을 사용할 수 있기 때문에, 아래 목록에 있는 파일도 실제로 사용 중일 가능성이 있다는 점을 주의해주세요.",
+       "unusedimagestext-categorizedimgisused": "다음의 파일이 존재하지만 어느 문서에도 포함되어 있지 않습니다. 분류된 이미지는 문서에 포함되어 있지 않음에도 사용되는 것으로 간주됩니다.\n다른 웹사이트가 파일을 직접 URL에 링크할 수 있으므로 실제 사용되지 않는 경우 여기에 나열될 수 있음을 참고해 주십시오.",
        "unusedcategoriestext": "사용하지 않는 분류 문서의 목록입니다.",
        "notargettitle": "해당하는 문서 없음",
        "notargettext": "기능을 수행할 대상 문서나 사용자를 지정하지 않았습니다.",
        "alreadyrolled": "[[:$1]]에서 [[User:$2|$2]] ([[User talk:$2|토론]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])의 편집을 되돌릴 수 없습니다.\n누군가가 이미 문서를 고치거나 되돌렸습니다.\n\n마지막으로 이 문서를 편집한 사용자는 [[User:$3|$3]] ([[User talk:$3|토론]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]])입니다.",
        "editcomment": "편집 요약: <em>$1</em>",
        "revertpage": "[[Special:Contributions/$2|$2]]([[User talk:$2|토론]])의 편집을 [[User:$1|$1]]의 마지막 판으로 되돌림",
+       "revertpage-anon": "[[Special:Contributions/$2|$2]]의 편집을 [[User:$1|$1]]의 마지막 판으로 되돌림",
        "revertpage-nouser": "숨긴 사용자의 편집을 {{GENDER:$1|[[User:$1|$1]]}}의 마지막 판으로 되돌림",
        "rollback-success": "{{GENDER:$3|$1}}의 편집을 되돌렸습니다.\n{{GENDER:$4|$2}}의 마지막 판으로 바뀌었습니다.",
        "sessionfailure-title": "세션 실패",
        "contribsub2": "{{GENDER:$3|$1}}($2)의 기여",
        "contributions-subtitle": "{{GENDER:$3|$1}}의 기여",
        "contributions-userdoesnotexist": "\"$1\" 사용자 계정은 등록되어 있지 않습니다.",
+       "negative-namespace-not-supported": "음수값의 이름공간은 지원되지 않습니다.",
        "nocontribs": "지정한 조건과 일치하는 바뀜을 찾을 수 없습니다.",
        "uctop": "최신",
        "month": "월:",
        "move-page-legend": "문서 이동",
        "movepagetext": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n원래의 문서는 새 문서로 넘겨주는 링크로만 남게 되고,\n원래 이름을 가리키는 넘겨주기는 자동으로 갱신됩니다.\n만약 이 설정을 선택하지 않았다면 [[Special:DoubleRedirects|이중 넘겨주기]]와 [[Special:BrokenRedirects|끊긴 넘겨주기]]를 확인해주세요.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 이름을 새 이름으로 입력했을 때는 그 문서가 넘겨주기 문서이고 문서 역사가 없어야만 이동이 됩니다. 그렇지 않을 경우에는 이동되지 <strong>않습니다</strong>.\n이것은 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>주의!</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해주세요.",
        "movepagetext-noredirectfixer": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n원래의 문서는 새 문서로 넘겨주는 링크로만 남게 됩니다.\n[[Special:DoubleRedirects|이중 넘겨주기]]와 [[Special:BrokenRedirects|끊긴 넘겨주기]]를 확인해주세요.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 이름을 새 이름으로 입력했을 때는 그 문서가 넘겨주기 문서이고 문서 역사가 없어야만 이동이 됩니다. 그렇지 않을 경우에는 이동되지 <strong>않습니다</strong>.\n이것은 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>주의!</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해주세요.",
-       "movepagetext-noredirectsupport": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 제목을 새 제목으로 입력했을 때는 그 문서가 이동되지 <strong>않습니다</strong>.\n이것은 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>주의:</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해주세요.",
+       "movepagetext-noredirectsupport": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 제목을 새 제목으로 입력했을 때는 그 문서가 이동되지 <strong>않습니다</strong>.\n이는 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>참고:</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해 주세요.",
        "movepagetalktext": "이 칸에 체크하면, 딸린 토론 문서가 자동으로 이동됩니다. 다만 비어있지 않은 토론 문서가 있다면 이동되지 않습니다.\n\n이러한 경우에는 수동으로 이동하거나 합쳐야 합니다.",
        "moveuserpage-warning": "<strong>경고:</strong> 사용자 문서를 이동하려고 하고 있습니다. 사용자 문서만 이동되며 사용자 이름이 바뀌지 <strong>않는다</strong>는 점을 참고하세요.",
        "movecategorypage-warning": "<strong>경고:</strong> 분류 문서를 이동하려고 합니다. 해당 문서만 이동되고 옛 분류에 있는 문서는 새 분류 안에 다시 분류되지 <em>않음</em>을 참고하세요.",
        "permanentlink-revid": "판 ID",
        "permanentlink-submit": "판으로 이동",
        "newsection": "새 문단",
+       "newsection-page": "대상 문서",
        "newsection-submit": "문서로 이동",
        "dberr-problems": "죄송합니다! 이 사이트에 기술적인 문제가 발생하고 있습니다.",
        "dberr-again": "잠시 기다리고 나서 다시 불러오세요.",
        "logentry-block-block": "$1님이 {{GENDER:$4|$3}}님을 $5 {{GENDER:$2|차단했습니다}} $6",
        "logentry-block-unblock": "$1님이 {{GENDER:$4|$3}}님의 {{GENDER:$2|차단을 해제했습니다}}",
        "logentry-block-reblock": "$1 님이 {{GENDER:$4|$3}} 님의 차단 기간을 $5(으)로 {{GENDER:$2|바꾸었습니다}} $6",
+       "logentry-partialblock-block-page": "{{PLURAL:$1|문서}} $2",
+       "logentry-partialblock-block-ns": "{{PLURAL:$1|이름공간}} $2",
        "logentry-partialblock-block": "$1님이 {{GENDER:$4|$3}}님을 $7 편집하지 못하도록 $5 {{GENDER:$2|차단}}했습니다. $6",
        "logentry-suppress-block": "$1님이 {{GENDER:$4|$3}} 사용자를 $5 {{GENDER:$2|차단했습니다}} $6",
        "logentry-suppress-reblock": "$1 님이 {{GENDER:$4|$3}} 님의 차단 기간을 $5(으)로 {{GENDER:$2|바꾸었습니다}} $6",
        "linkaccounts": "계정 연결",
        "linkaccounts-success-text": "계정이 연결되었습니다.",
        "linkaccounts-submit": "계정 연결",
+       "cannotunlink-no-provider-title": "연결을 해제할 계정이 없습니다",
+       "cannotunlink-no-provider": "연결을 해제할 계정이 없습니다.",
        "unlinkaccounts": "계정 연결 해제",
        "unlinkaccounts-success": "계정의 연결이 해제되었습니다.",
        "authenticationdatachange-ignored": "인증 데이터 변경을 처리하지 못했습니다. 제공자를 설정하지 않으셨습니까?",
        "specialmute-success": "알림 미표시 환경 설정이 업데이트되었습니다. [[Special:Preferences|환경 설정]]에서 알림이 표시되지 않는 모든 사용자를 확인하십시오.",
        "specialmute-submit": "확인",
        "specialmute-label-mute-email": "이 사용자의 이메일 알림을 표시하지 않습니다",
-       "specialmute-header": "<b>{{BIDI:[[User:$1]]}}</b>의 알림 미표시 환경 설정을 선택해 주십시오.",
+       "specialmute-header": "사용자 <b>{{BIDI:[[User:$1]]}}</b>의 알림 미표시 환경 설정을 선택해 주십시오.",
        "specialmute-error-invalid-user": "요청한 사용자 이름을 찾을 수 없습니다.",
-       "specialmute-email-footer": "{{BIDI:$2}}의 이메일 환경 설정을 관리하려면 <$1>을(를) 방문해 주십시오.",
+       "specialmute-error-no-options": "알림 미표시 기능을 사용할 수 없습니다. 다음의 이유일 수 있습니다: 이메일 주소를 인증하지 않았거나 위키 관리자가 이 위키에 이메일 기능 및 이메일 블랙리스트를 비활성화했습니다.",
+       "specialmute-email-footer": "사용자 {{BIDI:$2}}의 이메일 환경 설정을 관리하려면 <$1>을(를) 방문해 주십시오.",
        "specialmute-login-required": "알림 미표시 환경 설정을 변경하려면 로그인해 주십시오.",
        "mute-preferences": "알림 미표시 환경 설정",
        "revid": "$1 판",
        "passwordpolicies-policy-maximalpasswordlength": "비밀번호는 적어도 $1 {{PLURAL:$1|자}} 미만이어야 합니다",
        "passwordpolicies-policy-passwordcannotbepopular": "비밀번호는 {{PLURAL:$1|저명한 비밀번호가 될|$1개의 저명한 비밀번호에 속할}} 수 없습니다",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "비밀번호는 가장 흔히 쓰이는 비밀번호 100,000개 목록에 속할 수 없습니다.",
+       "passwordpolicies-policyflag-forcechange": "로그인 시 변경 필요",
        "passwordpolicies-policyflag-suggestchangeonlogin": "로그인할 때 변경 제안",
+       "mycustomjsredirectprotected": "넘겨주기이면서 사용자 공간 안에 참조하고 있지 않으므로 이 자바스크립트 페이지를 편집할 권한이 없습니다.",
        "easydeflate-invaliddeflate": "주어진 컨텐츠가 적절히 압축되지 않았습니다",
        "unprotected-js": "보안 상의 이유로 자바스크립트는 보호되지 않은 문서로부터 불러올 수 없습니다. 미디어위키: 이름공간이나 사용자의 하위 문서에서만 자바스크립트를 만들어 주십시오.",
        "userlogout-continue": "로그아웃하시겠습니까?"
index 373ba26..70f259b 100644 (file)
        "editfont-monospace": "فونت هم عرض",
        "editfont-sansserif": "فونت بدون سریف",
        "editfont-serif": "فونت سریف",
-       "sunday": "یە شأمە",
-       "monday": "دوٙشمە",
-       "tuesday": "سەشمە",
-       "wednesday": "چار شأمأھ",
-       "thursday": "پأشأمە",
-       "friday": "جÙ\88Ù\99Ù\85Ù\8e",
-       "saturday": "شأمە",
-       "sun": "یە شأمأھ",
-       "mon": "دوٙشأمأھ",
-       "tue": "سەشأمە",
-       "wed": "چارشأمأھ",
-       "thu": "پأشأمە",
-       "fri": "جÙ\88Ù\85Ù\8e",
-       "sat": "شأمأھ",
-       "january": "أڤأل قأھارھ",
-       "february": "Ù\84Û\8cرÛ\8cØ´Ú¯Ù\88Ù\99Ù\86",
-       "march": "ئنهزینوٙن",
-       "april": "نۉروٙزماھ",
-       "may_long": "گۉلبارماھ",
-       "june": "جۉریش",
-       "july": "میڤە رأسوٙن",
-       "august": "مە گأرمە",
-       "september": "Ø´Û\8cÙ\86Û\8cارÙ\88Ù\99Ù\86",
-       "october": "مالبارکوٙنوٙن",
-       "november": "ئا سأردکوٙنوٙن",
-       "december": "ئا Ø±Û\8cجÛ\8cÚ©Ù\86Ù\88Ù\99Ù\86",
+       "sunday": "یه شمبد",
+       "monday": "دوشمبد",
+       "tuesday": "سەشمبد",
+       "wednesday": "چار شمبد",
+       "thursday": "پنشمبد",
+       "friday": "جÙ\85Ù\87",
+       "saturday": "شمبە",
+       "sun": "یە شمبد",
+       "mon": "دوشمبد",
+       "tue": "سە شمبد",
+       "wed": "چارشمبد",
+       "thu": "پیشمبد",
+       "fri": "جÙ\85Ù\8eÙ\87",
+       "sat": "شمبد",
+       "january": "ژانویه",
+       "february": "Ù\81Ù\88رÛ\8cÙ\87",
+       "march": "مارس",
+       "april": "آوریل",
+       "may_long": "مه",
+       "june": "ژوئن",
+       "july": "ژوئیه",
+       "august": "اوت",
+       "september": "سپتاÙ\85بر",
+       "october": "اکتبر",
+       "november": "نوامبر",
+       "december": "دساÙ\85بر",
        "january-gen": "أڤأل قأهارھ",
        "february-gen": "لیریشگوٙن",
        "march-gen": "ئنهزینوٙن",
        "october-gen": "مالبارکوٙنوٙن",
        "november-gen": "ئا سأردکوٙنوٙن",
        "december-gen": "ئا ریجیکنوٙن",
-       "jan": "أڤأل قأھارھ",
-       "feb": "Ù\84Û\8cرÛ\8cØ´Ú¯Ù\88Ù\99Ù\86",
-       "mar": "ئنهزینوٙن",
-       "apr": "نۉروٙزماھ",
-       "may": "گوٙلبار ماھ",
-       "jun": "جوٙریش",
-       "jul": "میڤە رأسوٙن",
-       "aug": "مە گأرمە",
-       "sep": "Ø´Û\8cÙ\86Û\8cارÙ\88Ù\99Ù\86",
-       "oct": "مالبارکوٙنوٙن",
-       "nov": "ئا سأردکوٙنوٙن",
-       "dec": "ئا Ø±Û\8cجکÙ\86Ù\88Ù\99Ù\86",
+       "jan": "ژانویه",
+       "feb": "Ù\81Ù\88رÛ\8cÙ\87",
+       "mar": "مارس",
+       "apr": "آوریل",
+       "may": "مه",
+       "jun": "ژوئن",
+       "jul": "ژوئیه",
+       "aug": "اوت",
+       "sep": "سپتاÙ\85بر",
+       "oct": "اکتبر",
+       "nov": "نوامبر",
+       "dec": "دساÙ\85بر",
        "january-date": "ژانویه $1",
        "february-date": "فوریه $1",
        "march-date": "مارس $1",
        "category-file-count-limited": "دومن الذکر {{PLURAL:$1|فایل هس|$1 فایلل هسن}} د او دسه جریانی.",
        "listingcontinuesabbrev": "دۉنبالە",
        "index-category": "بلگه یل ایندکس وابیده",
-       "noindex-category": "بلگه یل ایندکس نوابیده",
+       "noindex-category": "ولاگئل ایندکس نوابیده",
        "broken-file-category": "بلگه یل وا فایلل لینک اشکسه",
        "about": "درباره",
        "article": "بلگه محتوا",
        "mypage": "بلگه",
        "mytalk": "گأپ",
        "anontalk": "سی ای آدرس آی پی گپ بزه",
-       "navigation": "هدایت کیردأن",
+       "navigation": "هدایت کردن",
        "and": "&#32;ڤ",
        "faq": "اف آی کیو \" سوالل متداول \"",
        "actions": "عملیه یل",
        "namespaces": "هۉمدیرأنگل",
        "variants": "أنۉاع",
-       "navigation-heading": "مأنۉ ناۉ ۉری",
+       "navigation-heading": "منوی ناوبری",
        "errorpagetitle": "خطا",
        "returnto": "بازگشت ڤە $1.",
        "tagline": "زھ {{SITENAME}}",
-       "help": "Ù\87Ù\88Ù\99میاری",
-       "search": "جۉستأن",
-       "searchbutton": "جۉستأن",
+       "help": "Ù\87Ù\8fمیاری",
+       "search": "جُستن",
+       "searchbutton": "جُستن",
        "go": "رو",
-       "searcharticle": "رÛ\89",
+       "searcharticle": "برÙ\8eÙ\87",
        "history": "ڤیرگار ھ بألگە",
        "history_short": "ڤیرگار",
        "updatedmarker": "بهروز وابی تا موقع آخرین سیل کردن مو",
        "print": "چاپ",
        "view": "نما",
        "view-foreign": "نیما مئن  $1",
-       "edit": "ئÛ\8cصلاح",
+       "edit": "اصلاح",
        "edit-local": "اصلاح توضیحتل محلی",
        "create": "درست کردن",
        "create-local": "ڤأندن توٙضیحأتل مأحألی",
        "protect_change": "تغییر بی",
        "unprotect": "تغییر دائن حالت حفاظت",
        "newpage": "بألگە نۉ",
-       "talkpagelinktext": "گأپ",
+       "talkpagelinktext": "گپ",
        "specialpage": "بلگه مخصوص",
        "personaltools": "ئوزارگل سی خۉتی",
-       "talk": "قسە",
+       "talk": "گپ",
        "views": "نمایل",
-       "toolbox": "ئÙ\88زارگÛ\95",
+       "toolbox": "ابزارئÙ\84",
        "imagepage": "دیئن بلگه فایل",
        "mediawikipage": "دیئن بلگه پیوم",
        "templatepage": "دیئن بلگه قالب",
        "redirectedfrom": "(تصحیح مجدد زھ $1)",
        "redirectpagesub": "بلگه تصحیح و هدایت زه مجدد",
        "redirectto": "تأغییر دائن مأسیر ڤە:",
-       "lastmodifiedat": "ئÛ\8c Ø¨Ø£Ù\84Ú¯Û\95 Ø§Ø®Û\8cرا ØªØ£ØºÛ\8cÛ\8cر Ú¤ Ø¦Û\8cصÙ\84اح Ú¤Ø§Ø¨Û\8cÛ\95 Ù\85أئÙ\86Û\95 $1, Ù\85أئÙ\86Û\95 $2.",
+       "lastmodifiedat": "اÛ\8cÙ\86 Ù\88Ù\84اگ Ø¢Ø®Ø±Û\8cÙ\86â\80\8cبار Ù\85Ù\90Ù\86 $1 Ø³Ø§Ø¹Øª $2 Ø§ØµÙ\84اح Ù\88ابÛ\8cدÙ\87.",
        "viewcount": "ای بلگه قابل دسترسی وابیه {{PLURAL:$1|یه بار|$1 مدتل}}.",
        "protectedpage": "بلگه حفاظت وابیه",
        "jumpto": "پریدن ڤھ:",
        "jumptonavigation": "هدایت کردن",
-       "jumptosearch": "جۉستأن",
+       "jumptosearch": "جُستن",
        "view-pool-error": "وبشید ، سرور بیش زه حد بارگیری وابیه .\nکارورل زیادی ایخن ای بلگنه سل کنن.\nلطفا یه لحظه واسیت قبلیکه به خیت ای بلگنه مجددا سل کیت.\n$1",
        "generic-pool-error": "وبشید ، سرور بیش زه حد بارگیری وابیه .\nکارورل زیادی ایخن ای منوبنه سل کنن.\nلطفا یه لحظه واسیت قبلیکه به خیت ای منوبنه مجددا سل کیت.",
        "pool-timeout": "پایان زمون اتنظار سی قفل",
        "pool-queuefull": "صف استخر پر هسی",
        "pool-errorunknown": "خطا ناشناخته",
        "pool-servererror": "شمارنده سرویس استخر ور تیه نی ($1).",
-       "aboutsite": "پۉرۉجھ : دأربارھ{{SITENAME}}",
+       "aboutsite": "پروژه : دربارهٔ‌{{SITENAME}}",
        "aboutpage": "Project:دأربارھ",
        "copyright": "مطلب دومن $ 1 هس نکه خلاف هونو ذکر وابی.",
        "copyrightpage": "{{ns:project}}:کۉپی رایت",
        "currentevents": "اتفاقل جاری",
        "currentevents-url": "Project:اتفاقل جاری",
        "disclaimers": "ئینکار کنندھ یل",
-       "disclaimerpage": "Project:ئÛ\8cÙ\86کار Ú©Ø§Ø±Û\89أراÙ\86",
+       "disclaimerpage": "Project:تکذÛ\8cبâ\80\8c Ù\86Ù\88Ù\85Ù\90Û\8c Ø¹Ù\85Ù\88Ù\85Û\8c",
        "edithelp": "هوٙمیاری سی ئیصلاح",
-       "mainpage": "بألگە أصلی",
+       "mainpage": "ولاگ اصلی",
        "mainpage-description": "بألگە أصلی",
        "policy-url": "Project:خط مشی",
-       "portal": "دأرگاھ کارڤأرل",
-       "portal-url": "Project:دأرگاھ کارڤأرل",
+       "portal": "ورودی کاربرئل",
+       "portal-url": "Project:ورودی کاربرئل",
        "privacy": "خط مأشی رازداری",
-       "privacypage": "Project:خط Ù\85أشÛ\8c Ø±Ø§Ø²Ø¯Ø§Ø±Û\8c",
+       "privacypage": "Project:سÛ\8cاست Ù\85حرÙ\85اÙ\86Ù\87",
        "badaccess": "خطا دسترسی",
        "badaccess-group0": "ایسا اجازه انجام کاری که ایخستیده ندارین",
        "badaccess-groups": "او کاری که ایسا درخواست کردین فقط سی کارورانیه که مئنه ای  گروهن  {{PLURAL:$2|آن گروه|یکی زه گروه یل}}: $1.",
        "versionrequired": "یه نسخه زه نیازمندی یل ویکی مدیا\n$1",
        "versionrequiredtext": "یه نسخه زه ویکی مدیا($1) نیازمند ه وه استفاده زه ای بلگه\nبویین :[[مخصوص:نسخه|نسخه مخصوص]].",
        "ok": "خووه",
-       "retrievedfrom": "بازÛ\8cاÙ\81ت Ø²Ú¾ \"$1\"",
+       "retrievedfrom": "برگرÙ\81تÙ\87 Ù\88Ù\87 Â«$1»",
        "youhavenewmessages": "پیوم نو داری $1 ($2).",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|ایشا داریت}} $1 زه {{PLURAL:$3|یه کارور دیه|$3 کارورل}} ($2).",
        "youhavenewmessagesmanyusers": "ایشا $1 زه کارورل دیه داریت ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|یه پیوم نو|999=پیومل نو}}",
        "newmessagesdifflinkplural": "آخر {{PLURAL:$1|تغییر|999=تغییرل}}",
        "youhavenewmessagesmulti": "ایشا پیوم نو داریت مئنه\n$1",
-       "editsection": "ئÛ\8cصلاح",
+       "editsection": "اصلاح",
        "editold": "ئیصلاح",
        "viewsourceold": "دیئن منبع",
        "editlink": "ئیصلاح",
        "viewsourcelink": "دیئن سأرچیشمە",
-       "editsectionhint": "ئÛ\8cصÙ\84اح Ø¦Û\8c Ø¨Ø£خش: $1",
+       "editsectionhint": "اصÙ\84اح Ø§Û\8c Ø¨خش: $1",
        "toc": "مۉحتڤا یل",
        "showtoc": "نمایش",
        "hidetoc": "قائم",
        "site-atom-feed": "خأھ ڤأر خۉ Atom سی $1",
        "page-rss-feed": "خبرخو RSS سی «$1»",
        "page-atom-feed": "خیڤأر Atom سی «$1»",
-       "red-link-title": "(بألگە ۉجوٙد نارھ) $1",
+       "red-link-title": "$1 (ولاگه نیسی)",
        "sort-descending": "مرتب سازی وا صعودی",
        "sort-ascending": "مرتب سازی وا صعودی",
        "nstab-main": "بألگە",
        "nstab-template": "ئۉلگوٙ",
        "nstab-help": "بلگه هومیاری",
        "nstab-category": "دسە",
-       "mainpage-nstab": "بألگە أصلی",
+       "mainpage-nstab": "ولاگ اصلی",
        "nosuchaction": "چنی دستوری موجود نی",
        "nosuchspecialpage": "چنو بلگه مخصوصی نی",
        "error": "خطا",
        "createaccount-title": "اکانت سازی سی {{SITENAME}}",
        "login-abort-generic": "ورود ایشا ناموفق وابی - سقط وابی",
        "loginlanguagelabel": "زوٙوٙن:$1",
-       "pt-login": "ئÛ\89Û\8cدأÙ\86 Ú¤Ú¾ Ø³Û\8cستÙ\85",
+       "pt-login": "داخÙ\8fÙ\84 Ù\88ابÛ\8cدÙ\86",
        "pt-login-button": "ئۉیدن ۉھ سیستم",
-       "pt-createaccount": "راس Ú©Û\8cردأÙ\86 Ø­Û\8cسآÛ\89",
+       "pt-createaccount": "دÙ\8fرÙ\8fس Ú©Ù\90ردÙ\8eÙ\86 Ø­Ø³Ø§Ø¨",
        "pt-userlogout": "رأتن زھ سیستم",
        "changepassword": "تغییر رمز",
        "resetpass_announce": "سی پایان ورود ، ایشا واسی یه رمز جدید سیخوت به ونی.",
        "anoneditwarning": "<strong>هۉشدار:</strong> ئیشا نأڤایتە مئنە سیستم. ئای پی ئیشا سی عۉموٙم قابل رۉیأت هی أر ئیصلاحی بۉکۉنیت. أر ئیشا <strong>[$1 وروٙد]</strong> یا <strong>[$2 راس کیردأن یە حیسآۉ]</strong>, ئیصلاحل ئیشا بە حیسآۉ کارڤأری ئیشا ھشتە ئیڤان ڤا مۉنفأعیل حیسآڤل دیە..",
        "loginreqlink": "ئوٙییدن ڤە سیستم",
        "newarticletext": "ایشا یه لینک ۉھ یه بلگنه که هنی ۉجود نارنه دنبال کردیته.\nسی راس کردن ای بلگه،نوشتنه مئنه جعبه زیر شرۉع کنیت(بینیتۉ [$1 help page] سی اصلاعات اضافی).\nار ایشا ۉا خطا ایچه هیسیت، ری <strong>back</strong> button مرۉرگر ایشا کلیژ کیت.",
-       "noarticletext": "Ø£Ù\84اÙ\86 Ù\85أتÙ\86Û\8c Ù\85ئÙ\86Û\95 Ø¦Û\8c Ø¨Ø£Ù\84Ú¯Û\95 Ù\86Û\8c.\nئÛ\8cشا Ø¦Û\8cتأرÛ\8cد [[Special:Search/{{PAGENAME}}|جÛ\89ستأÙ\86 Ø³Û\8c Ø¹Û\89Ù\86ڤاÙ\86 Ø¦Û\8c Ø¨Ø£Ù\84Ú¯Û\95]] Ù\85ئÙ\86Û\95 Ø¨Ø£Ù\84Ú¯Û\95 Û\8cÙ\84Û\95 Ø¯Û\8cÛ\95.\n<span class=\"plainlinks\">[{{fullurl:{{#Ù\85أخصÙ\88Ù\99ص:Ù\86Û\8cÙ\85اÛ\8cÙ\84}}|بأÙ\84Ú¯Û\95={{FULLPAGENAMEE}}}} Ø¬Û\89ستأÙ\86 Ø³Û\8c Ù\86Û\8cÙ\85اÛ\8cÙ\84 Ù\85أربÙ\88Ù\99Ø·], Û\8cا [{{fullurl:{{FULLPAGENAME}}|ئÛ\8cÙ\82داÙ\85=ئÛ\8cصÙ\84اح}} Ø¦Û\8cصÙ\84اح Ú©Û\89 Ø¦Û\8c Ø¨Ø£Ù\84Ú¯Ù\86Û\95]</span>.",
+       "noarticletext": "اÛ\8c ØµÙ\81Ø­Ù\87 Ø§Û\8cسÙ\88 Ø¯Ø§Ø±Ø§Û\8c Ù\87Û\8cÚ\86 Ù\85تÙ\86Û\8c Ù\86Û\8cسÛ\8c.\nاÛ\8cشا Ø§Û\8cترÛ\8cد Ù\85Ù\90Ù\86 ØµÙ\81حئÙ\84 Ø¯Ù\8e [[Special:Search/{{PAGENAME}}| Ø¹Ù\86Ù\88اÙ\86 Ø§Û\8c ØµÙ\81Ø­Ù\87 Ù\86Ù\8eÙ\87 Ø¨Ø¬Ù\88رÛ\8cد]]Ø\8c\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Ø³Û\8cاÙ\87Ù\87â\80\8cÛ\8cÙ\84Ù\87 Ù\85رتبطÙ\87 Ø¨Ø¬Ù\88رÛ\8cÛ\8cد]Ø\8c\nÛ\8cا [{{fullurl:{{FULLPAGENAME}}|action=edit}} Ø§Û\8c ØµÙ\81Ø­Ù\87 Ù\86Ù\87 Ø¨Ø¬Ù\88رÛ\8cÛ\8cد]</span>.",
        "noarticletext-nopermission": "د حال جاری مأتنی مئنە ئی بألگە نیسس.\nئیشا ئیتأرید [[Special:Search/{{PAGENAME}}|search for this page title]] مئنە بألگل دیە، یا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>، ڤألی ئیشا نیتأرید ئی بألگنە راس بۉکۉنیت.",
        "editing": "درحال اصلاح $1",
        "creating": "راس کردن $1",
        "currentrevisionlink": "آخرین ۉرژن",
        "cur": "فیعلی",
        "last": "قأبلی",
+       "histfirst": "قدیمی‌ترین",
+       "histlast": "جدیدترین",
        "rev-delundel": "قابلیأت تأغییر دائن",
        "history-title": "تاریخچه اصلاحل $1",
        "difference-title": "فرخ ۉا بین تجدید نطرل \"$1\"",
-       "lineno": "سأطر $1:",
+       "lineno": "سطر $1:",
        "editundo": "لأغڤ",
        "diff-multi-sameuser": "({{PLURAL:$1|یه ۉرزن متۉسط|$1 ۉرژنل متۉسط}} تۉسط کارۉر مشابه نشۉ نۉابیه)",
        "searchresults": "نأتیجل جۉستأن",
        "searchprofile-everything-tooltip": "جۉستأن سی مۉحتڤا(شامل بألگل گأپ)",
        "searchprofile-advanced-tooltip": "جۉستأن مأئنە هۉمدیرأنگل سفارشی",
        "search-result-size": "$1 ({{PLURAL:$2|1 کألمە|$2 کألمل}})",
-       "search-redirect": "(تأغÛ\8cÛ\8cر Ù\85أسÛ\8cر $1)",
+       "search-redirect": "(تغÛ\8cÛ\8cر Ø±Ù\87 Ù\88 $1)",
        "search-section": "(قیسمأت $1)",
        "search-suggest": "آیا منطۉر ایشا ای بی:$1",
        "searchall": "ھأمە",
        "right-writeapi": "ئیستفادھ د نڤشتن ڤە صوٙرأت API",
        "newuserlogpage": "سیاهە راس کیردأن حیسآۉ",
        "enhancedrc-history": "ڤیرگار",
-       "recentchanges": "تأغÛ\8cÛ\8cرÙ\84 Ù\86Û\89",
+       "recentchanges": "تغÛ\8cÛ\8cرئÙ\84 Ù\86Ù\88",
        "recentchanges-legend": "گۉزینە یل تأغییرل أخیر",
        "recentchanges-summary": "شیار تأغییرل أخیر مئنە ئی بألگە ڤە ڤیکی .",
        "recentchanges-label-newpage": "ئی ئیصلاح یە بألگە نوٙ ئیسازھ",
        "rcshowhidemine": "$1 ئیصلاحل مۉ",
        "rcshowhidemine-show": "نشۉ دائن",
        "rcshowhidemine-hide": "قائم کیردأن",
-       "rclinks": "نیشۉ دائن ئاخأرین $1 تأغییر مئن $2 روٙز أخیر؛ $3",
+       "rclinks": "نشون داۮن آخرین $1 تغییرئل مِن $2 روز اخیر",
        "diff": "فأرخ",
        "hist": "گۉزاریش",
        "hide": "قائم کیردأن",
        "recentchangeslinked": "تأغییرل مأربوٙط",
        "recentchangeslinked-toolbox": "تأغییرل مأربوٙط",
        "recentchangeslinked-title": "تأغییرل مۉرتأبیط ڤا $1",
-       "recentchangeslinked-summary": "ئی بألگە خاص تأغییرل اخیر مأئنە بألگل لینک ڤابیدھ ڤە ئی بألگنە نیشۉ ادھ.\nبألگلی کە مأئنە [[Special:Watchlist|لیست پیگیری یل]] ئیشا هیسن بە شکل '''سیاھ''' نیشۉ دادھ ابۉن.",
+       "recentchangeslinked-summary": "نووم یه صفحه نَه وارۮ کنیت تا تغییرئل صفحئلی که و وو پیوند زده وابۮه  یا و وو پیوند گروتنه نَه بویینید. (سی سیل کردن اعضای یه رده، ورودی نَه و صورت {{ns:category}}:نووم رده وارد کنیت). تغییرئل من صفحئلی که من  [[Special:Watchlist|فهرست پی‌گیرییل ایشا]] هِسِن <strong>ضخیم</strong> نما ایجورِن.",
        "recentchangeslinked-page": "نۉم بألگە:",
        "recentchangeslinked-to": "نیشۉ دائن تأغییرل بألگلی کە ڤە بألگە دادھ بیە لینک دادھ شۉدنە بە جای",
        "upload": "بلم گیر کردن فایل",
        "filehist-comment": "توٙضیح",
        "imagelinks": "ئیستفادھ د فایل",
        "linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحَلِ}} زِر و ای عکس پیوند دارہ :",
-       "nolinkstoimage": "بأÙ\84Ú¯Û\95 Û\8cÙ\84Û\8c Ú©Û\95 Ú¤Û\95 Ø¦Û\8c Ù\81اÛ\8cÙ\84 Ù\84Û\8cÙ\86Ú© Ø¯Ø§Ø¦Ù\86Û\95 Ù\86Û\8c.",
+       "nolinkstoimage": "اÛ\8c Ù¾Ø±Ù\88Ù\86دÙ\87 Ù\85Ù\90Ù\86 Ù\87Û\8cÚ\86 ØµÙ\81Ø­Ù\87â\80\8cاÛ\8c Ù\88 Ú©Ø§Ø± Ù\86رتÙ\87.",
        "sharedupload-desc-here": "ئی فایل ز $1 ئوٙمائە ڤ شاید د پۉرۉجە یل دیە مورد ئیستفادھ ڤابین.\nتوٙضیحتل ری [$2 بألگە تۉضیح فایل] دوٙمین نیشۉ ڤابیە .",
        "upload-disallowed-here": "ئیشا نیتأریت ئی فایلنە بینڤیسیت",
-       "randompage": "بألگە بأختە کی",
+       "randompage": "ولاگ شانسی",
        "nbytes": "$1 {{PLURAL:$1|بایت|بایتل}}",
        "nmembers": "$1 {{PLURAL:$1|عضۉ|اعضۉل}}",
        "newpages": "بألگە یل نوٙ",
        "mycontris": "سأھمیل",
        "month": "مئنھ ای ماھ (ۉ قبل زھ ھۉ):",
        "year": "مئنھ ای سال (ۉ قبل زھ ھۉ):",
-       "whatlinkshere": "لینکل ئی بألگە",
+       "sp-contributions-submit": "جُستن",
+       "whatlinkshere": "لینکئل ای ولاگ",
        "whatlinkshere-title": "بألگل کە لینک دائنە ڤە \"$1\"",
        "whatlinkshere-page": "بألگە:",
        "linkshere": "لینک ھ بألگل دوٙمین الذیکر ڤە '''$2''':",
        "whatlinkshere-prev": "{{PLURAL:$1|قأبلی |مۉرید قأبلی$1}}",
        "whatlinkshere-next": "{{PLURAL:$1|بأعدی |مۉرید بأعدی $1}}",
        "whatlinkshere-links": "← لینکل",
-       "whatlinkshere-hideredirs": "$1 ØªØ£ØºÛ\8cÛ\8cرÙ\84 Ù\85Ø£سیر",
-       "whatlinkshere-hidetrans": "$1 ØªØ£Ø±Ø§Ú¯Û\89Ù\86جاÛ\8cÛ\8cØ´",
-       "whatlinkshere-hidelinks": "$1 لینکل",
+       "whatlinkshere-hideredirs": "$1 ØªØºÛ\8cÛ\8cر Ù\85سیر",
+       "whatlinkshere-hidetrans": "$1 Ø§Ø³ØªÙ\81ادÙ\87 Ù\88ابÛ\8cدÙ\87 Ù\85Ù\86 Ù\88Ù\84اگ",
+       "whatlinkshere-hidelinks": "$1 لینکئل",
        "whatlinkshere-filters": "فیلتیرل",
        "blocklink": "بسە بۉھ",
        "contribslink": "شۉراکأتل",
        "movelogpage": "نمایه جابجایی",
        "export": "بألگل صادرھ",
        "thumbnail-more": "گأپ کردن",
-       "tooltip-pt-userpage": "حیسآۉ کارڤأری ئیشا",
-       "tooltip-pt-mytalk": "بألگە گأپ ئیشا",
-       "tooltip-pt-preferences": "ئÛ\89Ù\84Ø£Ú¤Û\8cأتÙ\84 Ù\85Û\89",
+       "tooltip-pt-userpage": "ولاگ {{GENDER:|کاربری ایشا}}",
+       "tooltip-pt-mytalk": "ولاگ گپ {{GENDER:|ایشا}}",
+       "tooltip-pt-preferences": "ترجÛ\8cحئÙ\84 {{GENDER:|اÛ\8cشا}}",
        "tooltip-pt-watchlist": "لیست بألگلی کە ئیشا تأغییرل هۉنۉنە  دۉنبال ئیکۉنین",
        "tooltip-pt-mycontris": "لیست سأھمیل ئیشا",
-       "tooltip-pt-login": "توٙصیە ڤابوٙھ کە ڤە سیستم داخل بوٙین. أما ئیجباری نیسس",
+       "tooltip-pt-login": "توصیه ایکنیم که وه سیستم وارد وابید، گرچه اجباری نیسی",
        "tooltip-pt-logout": "رأتن زھ سیستم",
        "tooltip-pt-createaccount": "توٙصیە ڤابوٙھ کە حسآڤ کارڤأری راس بکنیت یا ڤە سیستم داخل بوٙین. اما ئیجباری نیسس",
-       "tooltip-ca-talk": "قسە د بألگە مۉحتڤا",
+       "tooltip-ca-talk": "گپئل دربارهٔ محتوِی ولاگ",
        "tooltip-ca-edit": "ایسھ ترین ای بلگھ نھ اصلاح کنیت.لطفا قبل اصلاح ای بلگھ ز دۉکمه پیش نمایش استفاده کنیت",
        "tooltip-ca-addsection": "ئاغاز کیردأن یە قسمت نوٙ",
        "tooltip-ca-viewsource": "ئی بألگە دوٙمین حیمایأتە. \nئیشا تأرین سأرچیشمە سە بیخوٙنیت",
        "tooltip-ca-history": "ڤیرگار",
        "tooltip-ca-move": "جابجاکردن ای بلگه",
        "tooltip-ca-watch": "ئیضاف کردن ئی بألگە ڤە لیست پیگیری یل ئیشا",
-       "tooltip-search": "جۉستأن {{SITENAME}}",
-       "tooltip-search-go": "رÛ\89 Ù\85أئÙ\86Ù\87 Ø¨Ø£Ù\84Ú¯Û\95 Ø¦Û\8c Ú¤Ø§ Ø¦Û\8c Ù\86Û\89Ù\85 Ø£Ø± Ù\87Û\8cسس",
-       "tooltip-search-fulltext": "جۉستأن بألگە یل سی ئی مأتن",
+       "tooltip-search": "جُستن {{SITENAME}}",
+       "tooltip-search-go": "أر Ø§Û\8cترÛ\8cد Ù\88Ù\87 Ù\88Ù\84اگÛ\8c Ø¨Û\8c Ù\87Ù\85Û\8c Ù\86Ù\88Ù\88Ù\85 Ø¨Ø±Û\8cد.",
+       "tooltip-search-fulltext": "جُستن ای جمله مِن ولاگئل",
        "tooltip-p-logo": "رۉ د بألگە أصلی",
        "tooltip-n-mainpage": "رۉ د بألگە أصلی",
        "tooltip-n-mainpage-description": "رۉ د بألگە أصلی",
        "tooltip-n-recentchanges": "سیائل تأغییرل آخر مئن ئی ڤیکی",
        "tooltip-n-randompage": "سڤار کردن یە بألگە بأختە کی",
        "tooltip-n-help": "ینە جا سی سیل کردن",
-       "tooltip-t-whatlinkshere": "فهرست همە بألگە یل ڤیکی کە ئیچوٙ لینک دارن",
+       "tooltip-t-whatlinkshere": "فهرست همِی صفحئلی که وه ای صفحه پیوند ایخرن",
        "tooltip-t-recentchangeslinked": "تأغییرل آخر مئن بألگە کە لینک دانە ڤە ئی بألگە",
        "tooltip-feed-atom": "تأغذیە کچک تأرین جۉزء  ئی بألگە",
-       "tooltip-t-contributions": "یە لیست ز مۉشاریکأت کۉنأندھ یل ڤ مأقالە دهأندھ یل ئی بألگە",
+       "tooltip-t-contributions": "لیست مشارکتئل توسط {{GENDER:$1|این کاربر}}",
        "tooltip-t-upload": "بلم گیر کردن فایلل",
-       "tooltip-t-specialpages": "بألگە یل ڤیجە",
-       "tooltip-t-print": "Ù\88Û\8cرÚ\98Ù\86 Ø³Û\8c Ú\86اپ Ø¦Û\8c Ø¨Ø£Ù\84Ú¯Û\95",
+       "tooltip-t-specialpages": "فهرستی وه همی ولاگئل ویژه",
+       "tooltip-t-print": "Ù\86سخÙ\90Û\8c Ù\82ابÙ\84 Ú\86اپ Ø§Û\8c ØµÙ\81Ø­Ù\87",
        "tooltip-t-permalink": "لینکل دائمی ڤە ئی ۉیرژن ئی بألگە",
        "tooltip-ca-nstab-main": "دیئن بألگە مۉحتڤا",
        "tooltip-ca-nstab-user": "دیئن بألگە کارڤأر",
-       "tooltip-ca-nstab-special": "ئی بألگە ھا ڤیجە ڤ ئیشا نیتأرین خۉد ئی بألگنە ئیصلاح کنیت",
+       "tooltip-ca-nstab-special": "یو یه صفحِیْ ویژه ییَ، و قابل اصلاح نیسی",
        "tooltip-ca-nstab-project": "دیئن بلگه پرۉجه",
        "tooltip-ca-nstab-image": "دیئن بألگە فایل",
        "tooltip-ca-nstab-template": "دیئن قالیب",
        "tooltip-rollback": "\"اعادە\" ۉرگأردوٙندأن بە ڤأضع أڤألیە سی ئی بألگە کە سی مۉشارکأت  ئاخر ئیصلاح ڤابیدھ ڤا یە کلیک",
        "tooltip-undo": "\"لأغڤ\" ڤۉرگأشت ئی ئیصلاح ڤ ڤا ڤیدن فۉرم ئیصلاح مئنە پیش نیمایش.ئیجازھ ئیدھ کە یە دألیل ڤە خۉلاصە ئیضافە بۉکۉنی.",
        "tooltip-summary": "یە خۉلاصە کچکی بینڤیسیت",
-       "simpleantispam-label": "ئÛ\8cÙ\86تÛ\8cخاب Ø¦Ø§Ù\86تÛ\8c-ئÛ\8cسپÛ\8cÙ\85\nÙ¾Û\89ر <strong>Ù\86Ø£Ú©Û\89Ù\86Û\8cت</strong> Ø¦Û\8cÙ\86Û\95 Ù\85ئÙ\86!",
+       "simpleantispam-label": "بررسÛ\8c Ø¶Ø¯ Ù\87رزÙ\86گارÛ\8c.\nاÛ\8c Ù\82سÙ\85تÙ\87 Ù¾Ø± <strong>Ù\86Ú©Ù\86Û\8cت</strong>!",
        "pageinfo-toolboxlink": "اطلاعات بألگە",
+       "pageinfo-contentpage-yes": "ها",
        "previousdiff": "← اصلاح قدیمی",
        "nextdiff": "اصلاح نۉتر→",
        "file-info-size": "$1 × $2 پیکسل, اندازھ فایل: $3, MIME نۉع: $4",
        "monthsall": "همه",
        "semicolon-separator": "؛&#32;",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|گأپ]])",
-       "specialpages": "بألگە یل ڤیجە",
+       "specialpages": "ولاگئل ویژه",
        "tag-filter": "[[Special:Tags|بأرچأسب]] فیلتر:",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|بأرچسب|بأرچسبل}}]]: $2",
        "logentry-delete-delete": "$1 {{GENDER:$2|حأذف ڤابیدھ}} بألگە $3",
        "logentry-move-move": "$1 {{GENDER:$2|انتقال دادھ بیه}} بلگه $3 ۉھ $4",
        "logentry-newusers-create": "حسآۉ کارڤأر $1 ڤابیە {{GENDER:$2|راس ڤیدھ }}",
        "logentry-upload-upload": "$1 {{GENDER:$2|بلم گیر کردھ ۉابی}} $3",
-       "searchsuggest-search": "جۉستأن",
+       "searchsuggest-search": "جُستن",
        "specialmute": "بی‌صدا",
        "userlogout-continue": "ایخیت برِیِتو وَدَر"
 }
index 12f69f0..b56f065 100644 (file)
@@ -64,6 +64,7 @@
        "tog-norollbackdiff": "Jan tampilan pabedoan sasudah malakukan pangambalian",
        "tog-useeditwarning": "Ingekan denai jikok maninggakan laman suntiang sabalun manyimpan parubahan",
        "tog-prefershttps": "Selalu gunokan koneksi aman katiko masuak log",
+       "tog-showrollbackconfirmation": "Tampilkan konfirmasi katiko mangklik pautan pangambalian",
        "underline-always": "Taruih",
        "underline-never": "Indak pernah",
        "underline-default": "Kulik atau pangaturan paramban web",
        "returnto": "Baliak ka $1",
        "tagline": "Dari {{SITENAME}}",
        "help": "Bantuan",
+       "help-mediawiki": "Bantuan pakaro MediaWiki",
        "search": "Cari",
        "searchbutton": "Cari",
        "go": "Tuju",
        "searcharticle": "Tuju",
        "history": "Riwayaik laman",
-       "history_short": "Riwayaik",
-       "history_small": "riwayaik",
+       "history_short": "Versi",
+       "history_small": "versi",
        "updatedmarker": "alah diubah samanjak kunjuangan tarakhia Sanak",
        "printableversion": "Versi cetak",
        "permalink": "Pautan parmanen",
        "nospecialpagetext": "<strong>Sanak mamintak laman istimewa nan indak sah.</strong>\n\nDaftar laman istimewa nan sah dapek dicaliak di [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Kasalahan",
        "databaseerror": "Kasalahan basis data",
+       "databaseerror-text": "Ado gangguan pado kueri basis data. Iko mungkin manunjuakkan adonyo kasalahan pado parangkek lunak.",
+       "databaseerror-textcl": "Ado gangguan pado kueri basis data.",
+       "databaseerror-query": "Kueri: $1",
        "databaseerror-function": "Fungsi: $1",
        "databaseerror-error": "تېروتنه: $1",
        "transaction-duration-limit-exceeded": "Untuak mancagah panundoan replikasi nan tinggi, pangiriman ko dibatalan karano lamo panulihan $1 malabiahi bateh $2.\nJiko sanak nio maubah banyak hal dalam sakali ubah, cubo lakuan dalam operasi nan labiah ketek.",
        "cannotdelete-title": "Indak dapek mangapuih laman \"$1\"",
        "delete-scheduled": "Laman \"$1\" dijadwalan untuak diapuih. Mohon basaba.",
        "delete-hook-aborted": "Pengapusan batal jo hook.\nIndak ado keterangan.",
+       "no-null-revision": "Indak dapek mambuek paubahan kosong nan baru untuak laman \"$1\"",
        "badtitle": "Judul indak sah",
        "badtitletext": "Pamintaan judul laman indak sah, kosong, atau antarbaso atau antarwiki nan salah sambuang. Mungkin juo ado kandungan karakter nan indak buliah digunoan untuak judul.",
        "title-invalid-empty": "Judul laman nan dimintak kosong atau hanyo barisi namo sabuah ruang namo.",
        "botpasswords-label-cancel": "Batalan",
        "botpasswords-label-delete": "Hapuih",
        "botpasswords-label-resetpassword": "Setel ulang kato sandi",
+       "botpasswords-label-grants-column": "Izin diagiah",
        "botpasswords-bad-appid": "Namo bot \"$1\" indak sah.",
        "botpasswords-insert-failed": "Gagal manambah namo bot \"$1\". Alah ditambahan sabalun iko?",
        "botpasswords-update-failed": "Gagal mampabarui namo bot \"$1\". Pernah dihapuih sabalunnyo?",
        "botpasswords-updated-body": "Kato sandi untuak bot \"$1\" dari {{GENDER:$2|user}} \"$2\" alah dipabarui.",
        "botpasswords-deleted-title": "Kato sandi bot dihapuih",
        "botpasswords-deleted-body": "Kato sandi untuak bot \"$1\" dari {{GENDER:$2|user}} \"$2\" berhasil dihapuih.",
+       "botpasswords-newpassword": "Kato sandi baru untuak masuak log jo <strong>$1</strong> adolah <strong>$2</strong>. <em>Cataiklah kato sandi ko untuak rujuakan ka muko.</em> <br> (Untuak bot lamo nan mamaralukan namo masuak log nan samo jo namo pangguno, dapek manggunokan <strong>$3</strong> sabagai namo pangguno jo <strong>$4</strong> sabagai kato sandi.)",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider indak tasadio.",
        "botpasswords-restriction-failed": "Bateh dalam kato sandi mangahalangi masuak log ko.",
        "botpasswords-invalid-name": "Namo pangguno nan diagiah indak manganduang pamisah kato sandi bot (\"$1\").",
        "botpasswords-not-exist": "Pangguno \"$1\" indak mampunyoi kato sandi bot banamo \"$2\".",
        "passwordreset-emailelement": "Namo pangguno: \n$1\n\nSandi samantaro: \n$2",
        "passwordreset-emailsentemail": "Jiko alamaik surel ko bahubuangan jo akun Sanak, surel parubahan kato sandi akan dikirim.",
        "passwordreset-emailsentusername": "Jiko ado alamaik surel nan bahubuangan jo namo pangguno ko, surel untuak mangatua ulang kato sandi akan dikirim.",
+       "passwordreset-nocaller": "Paimbau musti disadiokan",
+       "passwordreset-nosuchcaller": "Paimbau indak ado: $1",
+       "passwordreset-ignored": "Pamuliahan kato sandi indak tatangani. Mungkin panyadio indak diatua?",
        "passwordreset-invalidemail": "Alamaik surel indak sah",
        "passwordreset-nodata": "Namo pangguno ataupun alamai surel indak diaagiahan",
        "changeemail": "Tuka atau hapuih alamaik surel.",
        "changeemail-throttled": "Sanak alah acok bana mancubo masuak log. Mohon tunggu $1 sabalun mancubo baliak.",
        "changeemail-nochange": "Mohon masuakan alamaik surel nan lain.",
        "resettokens": "Ubah token",
+       "resettokens-text": "Sanak dapek ma-reset Token nan mamungkinkan akses ka data pribadi tatantu nan takaik jo akun Sanak di siko.\n\nSanak musti malakukannyo kok Sanak sacaro indak sangajo babagi jo urang lain atau kok akun Sanak alah disalinoki.",
+       "resettokens-no-tokens": "Indak ado token untuak di-reset.",
+       "resettokens-tokens": "Token:",
        "resettokens-token-label": "$1 (nilai saat ini:$2)",
+       "resettokens-watchlist-token": "Token untuak sindikasi web (Atom/RSS) dari [[Special:Watchlist|parubahan di daftar pantauan Sanak]]",
+       "resettokens-done": "Reset token.",
+       "resettokens-resetbutton": "Reset token nan dipiliah",
        "bold_sample": "Teks taba",
        "bold_tip": "Teks taba",
        "italic_sample": "Teks miriang",
        "previewerrortext": "Ado nan salah wakatu manunjuakan pratinjau parubahan Sanak.",
        "blockedtitle": "Pangguno diblokir",
        "blocked-email-user": "<strong>Namo pangguno Sanak diblokir untuak mangirim surel. Sanak masih bisa manyuntiang laman lain di wiki ko. </strong> Sanak bisa mancaliak parincian sakek pado [[Special:MyContributions|jariah pangguno]].\n\nSakek dilakuan dek $1.\n\nAlasannyo adolah <em>$2</em>.\n\n* Disakek sajak: $8\n* Sakek kadaluarsa pado: $6\n* Sasaran panyakek: $7\n* ID sakek #$5",
+       "blockedtext-partial": "<strong>Namo pangguno Sanak disakek untuak mangirim surel. Sanak masih bisa manyuntiang laman lain di wiki ko. </strong> Sanak bisa mancaliak parincian sakek pado [[Special:MyContributions|jariah pangguno]].\n\nSakek dilakuan dek $1.\n\nAlasannyo adolah <em>$2</em>.\n\n* Disakek sajak: $8\n* Sakek kadaluarsa pado: $6\n* Sasaran panyakek: $7\n* ID sakek #$5",
        "blockedtext": "'''Namo pangguno atau alamaik IP Sanak alah kanai sakek.'''\n\nSakek dibuek dek $1.\nAlasan nan diagiahan adolah ''$2''.\n\n* Kanai sakek sajak: $8\n* Maso sakek habih pado: $6\n* Sasaran nan disakek: $7\n\nSanak dapek maubungi $1 atau [[{{MediaWiki:Grouppage-sysop}}|panguruih lainnyo]] untuak marundiangan hal ko.\n\nSanak indak dapek manggunoan fitua 'Kirim surel ka pangguno ko' kacuali Sanak alah mamasuakan alamaik surel nan sah di [[Special:Preferences|pangaturan akun]] dan Sanak indak sadang disakek untuak manggunoannyo.\n\nAlamaik IP Sanak adolah $3, dan ID panyakek adolah $5.\nTolong saratoan informasi di ateh pado satiok patanyoan nan Sanak buek.",
        "autoblockedtext": "Alamaik IP Sanak alah kanai sakek sacaro otomatih dek dipakai jo pangguno lain, nan alah disakek dek $1. Alasannyo dek:\n\n:<em>$2</em>\n\n* Kanai sakek sajak: $8\n* Maso sakek habih pado: $6\n* Sasaran nan disakek: $7\n\nSanak dapek maubuangi $1 atau [[{{MediaWiki:Grouppage-sysop}}|panguruih lainnya]] untuak marundiangan pakaro ko.\n\nSanak indak dapek manggunoan pakakeh \"{{int:emailuser}}\" kacuali Sanak alah mamasuakan alamaik surel nan sah pado [[Special:Preferences|pangaturan akun]] dan Sanak indak sadang disakek untuak manggunoannyo.\n\nAlamaik IP Sanak adolah $3, dan ID panyakekan adolah $5.\nTolong saratoan informasi di ateh pado satiok patanyaan nan Sanak buek.",
+       "systemblockedtext": "Namo pangguno atau alamaik IP Sanak alah disakek sacaro otomatis dek MediaWiki.\nAlasan nan diagiah adolah:\n\n:<em>$2</em>\n\n* Disakek sajak: $8\n* Sakek kadaluwarsa pado: $6\n* Sasaran panyakekan: $7\n\nAlamaik IP Sanak kini adolah $3\nMohon saratokan sadoalah parincian di ateh dalam satiok patanyoan nan Sanak ajukan.",
        "blockednoreason": "indak ado alasan nan diagiah.",
+       "blockedtext-composite-ids": "Panyakekan ID relevan: $1 (alamaik IP Sanak juo dapek dicekal)",
+       "blockedtext-composite-no-ids": "Alamaik IP Sanak muncua dalam daftar itam gando",
+       "blockedtext-composite-reason": "Ado panyakekan bagando ka bakeh akun Sanak dan/atau alamaik IP Sanak.",
        "whitelistedittext": "Sanak musti $1 untuak manyuntiang laman.",
        "confirmedittext": "Sanak musti mangkonfirmasian alamaik surel sabalun manyuntiang laman.\nMasuakan dan validasian alamaik surel Sanak pado [[Special:Preferences|pangaturan pangguno]] Sanak.",
        "nosuchsectiontitle": "Bagian indak ditamuan",
        "userjsonyoucanpreview": "<strong>Tip:</strong> Gunoan tombol \"{{int:showpreview}}\" untuak mauji JSON baharu Sanak sabalun manyimpannyo.",
        "userjsyoucanpreview": "'''Tips:''' Gunoan tombol \"{{int:showpreview}}\" untuak mauji JS baharu Sanak sabalun manyimpannyo.",
        "usercsspreview": "<strong>Ingeklah bahawa Sanak sadang manampilan pratinjau dari CSS Sanak.\nPratinjau ko alun disimpan!</strong>",
+       "userjsonpreview": "<strong>Ingeklah baso nan Sanak caliak anyolah pratonton JavaScript Sanak, dan pratonton tasabuik alun disimpan!</strong>",
        "userjspreview": "<strong>Ingeklah bahawa nan Sanak liek hanyolah pratinjau JavaScript Sanak, dan pratinjau tasabuik alun disimpan!</strong>",
        "sitecsspreview": "<strong>Ingeklah Sanak hanyo manampilan pratinjau dari CSS ko. Parubahan alun disimpan!</strong>",
        "sitejsonpreview": "<strong>Ingeklah bahwa Sanak hanyo manampilan pratinjau konfigurasi JSON ko. Parubahan alun basimpan!</strong>",
        "yourtext": "Teks Sanak",
        "storedversion": "Versi tasimpan",
        "editingold": "'''Paringatan:\nSanak manyuntiang revisi lamo suatu laman.\nJikok Sanak manyimpannyo, parubahan-parubahan nan dibuek sajak revisi ko akan hilang.'''",
+       "unicode-support-fail": "Tampaknyo masin pancari Sanak indak mandukuang Unicode, nan manjadi syaraik panyuntiangan laman. Jadi suntiangan Sanak indak disimpan.",
        "yourdiff": "Pambedoan",
        "copyrightwarning": "Untuak diingek bahaso apo nan disumbang kapado {{SITENAME}} dianggap lah dilapeh di bawah $2 (caliak $1 untuak langkoknyo).\nJikok awak indak ingin apo nan ditulih tu disuntiang dan disebaran, jan dikirim tulisan tu ka siko.<br />\nAwak musti bajanji juo bahaso iko adolah asia karya awak surang, atau disalin dari sumber miliak basamo atau sumber bebas lainnyo.\n'''Jan dikirim karya bahak cipta nan indak baizin!'''",
        "copyrightwarning2": "Parhatikan sadoalah jariah tahadok {{SITENAME}} dapek disuntiang, diubah, atau dihapuih dek panyumbang lainnyo. Jikok Sanak indak ingin tulisan Sanak disuntiang urang lain, jan kiriman ka siko.<br />Sanak juo bajanji iko adolah hasil karya Sanak surang, atau disalin dari sumber miliak umum atau sumber bebas nan lain (liek $1 untuak informasi labiah lanjuik). '''JAN KIRIMAN KARYA NAN DILINDUNGI HAK CIPTA TANPA IJIN!'''",
        "nocreate-loggedin": "Sanak ndak mampunyoi hak akses untuak mambuek laman baharu.",
        "sectioneditnotsupported-title": "Panyuntiangan bagian indak didukuang",
        "sectioneditnotsupported-text": "Panyuntiangan bagian indak didukuang di laman suntiang iko.",
+       "modeleditnotsupported-title": "Panyuntiangan indak didukuang",
+       "modeleditnotsupported-text": "Panyuntiangan indak didukuang untuak model konten $1.",
        "permissionserrors": "Kasalahan Hak Akses",
        "permissionserrorstext": "Sanak indak ado hak untuak malakuannyo dek {{PLURAL:$1|alasan}} barikuik:",
        "permissionserrorstext-withaction": "Sanak indak punyo hak akses untuak $2, dek {{PLURAL:$1|alasan}} barikuik:",
        "edit-no-change": "Suntiangan sanak ditulak, karano indak ado parubahan nan tajadi ka teks.",
        "edit-slots-cannot-add": "{{PLURAL:$1|slot is|slots are}} barikuik indak didukuang disiko: $2.",
        "edit-slots-cannot-remove": "{{PLURAL:$1|slot is|slots are}} wajib dan indak  buliah dihapuih: $2.",
+       "edit-slots-missing": "{{PLURAL:$1|slot is|slots are}} barikuik indak didukuang disiko: $2.",
        "postedit-confirmation-created": "Laman alah dibuek.",
        "postedit-confirmation-restored": "Laman alah dipuliahan.",
        "postedit-confirmation-saved": "Suntiangan Sanak alah tasimpan.",
        "invalid-content-data": "Data kanduangan indak valid.",
        "content-not-allowed-here": "Isi \"$1\" indak diizinkan di laman [[:$2]] pado slot \"$3\"",
        "editwarning-warning": "Maninggakan laman ko dapek maakibaikan parubahan nan dibuek hilang. Jikok Sanak lah masuak log, dapek mamatian pasan ko malalui bagian \"{{int:prefs-editing}}\" pado laman Pangaturan.",
+       "editpage-invalidcontentmodel-title": "Model konten indak didukuang",
+       "editpage-invalidcontentmodel-text": "Model konten \"$1\" indak didukuang.",
+       "editpage-notsupportedcontentformat-title": "Format konten indak didukuang",
+       "editpage-notsupportedcontentformat-text": "Format konten $1 indak didukuang dek model konten $2.",
        "slot-name-main": "Utamo",
        "content-model-wikitext": "Teks wiki",
        "content-model-text": "Teks kosong",
        "content-model-css": "CSS",
        "content-json-empty-object": "Objek kosong",
        "content-json-empty-array": "Lariak kosong",
+       "unsupported-content-model": "<strong> Paringatan: </strong> Model konten $1 indak didukuang di wiki ko.",
+       "unsupported-content-diff": "Panyuntiangan indak didukuang untuak model konten $1.",
+       "unsupported-content-diff2": "Pabedaan antaro model konten $1 jo $2 indak didukuang di wiki ko.",
        "deprecated-self-close-category": "Laman nan menggunoan tag HTML tatutuik-surang indak sah",
+       "deprecated-self-close-category-desc": "Laman ko manganduang tag HTML tatutuik-surang nan indak sah, sarupo <code>&lt;b/></code> atau <code>&lt;span/></code>.  Parilaku tag sarupo ko ka sagiro barubah supayo konsisten jo spesifikasi HTML5, jadi panggunoannyo dalam teks wiki indak lai disarankan.",
        "expensive-parserfunction-warning": "'''Paringatan:''' Laman ko manganduang talalu banyak panggilan fungsi parser.\n\nSeharusnyo kurang dari $2 {{PLURAL:$2|panggilan}}, tapi {{PLURAL:$1|kini ado $1 panggilan}}.",
        "expensive-parserfunction-category": "Laman nan talalu banyak panggilan fungsi parser",
        "post-expand-template-inclusion-warning": "'''Peringatan:''' Ukuran templat talalu gadang.\nBabarapo templat akan diabaikan.",
        "undo-failure": "Suntiangan ko indak dapek dibatalan dek konflik panyuntiangan antaro.",
        "undo-norev": "Suntiangan ko indak dapek dibatalan dek laman indak ditamukan atau lah dihapuih.",
        "undo-summary": "Mambatalan revisi $1 oleh [[Special:Contributions/$2|$2]] ([[User talk:$2|maota]])",
+       "undo-summary-anon": "Baliakan revisi $1 dek [[Special:Contributions/$2|$2]]",
        "cantcreateaccount-text": "Mambuek akun dari alamat IP ko ('''$1''') alah diblok jo [[User:$3|$3]].\n\nAlasan nan diagiah jo $3 adolah ''$2''",
        "viewpagelogs": "Caliak log untuak laman ko",
        "nohistory": "Indak ado sajarah panyuntiangan untuak laman ko",
        "action-writeapi": "manggunoan panulisan API",
        "action-import": "impor laman dari wiki lain",
        "nchanges": "$1 {{PLURAL:$1|parubahan}}",
-       "enhancedrc-history": "riwayaik",
+       "enhancedrc-history": "versi",
        "recentchanges": "Parubahan baru",
        "recentchanges-legend": "Piliahan parubahan baru",
        "recentchanges-summary": "Caliak parubahan baru di wiki pado laman ko.<br />\n;Patunjuak:(<span style=\"color:blue;\">bedo</span>) parubahan, (<span style=\"color:blue;\">sijarah</span>) riwayaik parubahan, '''B''' laman baru, '''b''' suntiangan bot, '''k''' suntiangan ketek, <span class=\"unpatrolled\">!</span> parubahan alun dipatroli,<br /><span style=\"color:green;\">'''(+ ''bita'')'''</span> isi laman batambah, <span style=\"color:red;\">(- ''bita'')</span> isi laman bakurang, (← Ikhtisar otomatih), (→ <span style=\"color:grey;\">Suntiangan bagian</span>)",
index e63cd39..410e3ab 100644 (file)
        "undo-norev": "Измената не можеше да биде вратена бидејќи не постои или била избришана.",
        "undo-nochange": "Се чини дека измената (уредувањето) е веќе вратена.",
        "undo-summary": "Откажано уредувањето $1 на уредникот [[Special:Contribs/$2|$2]] ([[User talk:$2|разговор]])",
+       "undo-summary-anon": "Отповикај ја преработката $1 на [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Поништи ја преработката $1 на скриен корисник",
        "cantcreateaccount-text": "Создавањето на корисничка сметка од оваа IP-адреса (<strong>$1</strong>) е блокирано од страна на [[User:$3|$3]].\n\nОбразложението дадено од страна на $3 е <em>$2</em>",
        "cantcreateaccount-range-text": "Создавањето на сметки од IP-адреси во опсегот <strong>$1</strong> каде спаѓа вашата IP-адреса (<strong>$4</strong>) е блокирано од корисникот [[User:$3|$3]].\n\n$3 ја наведе следнава причина: <em>$2</em>",
        "cantrollback": "Уредувањето не може да се отповика.\nПоследниот уредник е воедно и единствениот автор на страницата.",
        "alreadyrolled": "Не може да се отповика последното уредување на страницата „[[:$1]]“ извршено од  [[User:$2|$2]] ([[User talk:$2|разговор]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nнекој друг веќе ја изменил или отповикал страницата.\n\nПоследното уредување го изврши [[User:$3|$3]] ([[User talk:$3|разговор]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Коментарот на уредувањето беше: <em>$1</em>.",
-       "revertpage": "Отстрането уредувањето на [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]]), вратено на последната верзија на [[User:$1|$1]]",
+       "revertpage": "Отповикани уредувањата на [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]]), враќајќи на последната преработка на [[User:$1|$1]]",
+       "revertpage-anon": "Отповикани уредувања на [[Special:Contributions/$2|$2]], враќајќи на последната преработка на [[User:$1|$1]]",
        "revertpage-nouser": "Вратени уредувања од скриен корисник на последната преработка на {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Откажани уредувањата на {{GENDER:$3|$1}};\nвратено на последната верзија на {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Седницата не успеа",
index 7540d5c..17a46a4 100644 (file)
        "ok": "ꯌꯥꯔꯦ",
        "retrievedfrom": "\"$1\" ꯃꯐꯝꯗꯨꯗꯒꯤ ꯑꯣꯏꯔꯛꯄꯥ",
        "youhavenewmessages": "{{PLURAL:$3|ꯅꯪꯉꯣꯟꯗ ꯂꯩ}} $1 ($2) ꯫",
-       "youhavenewmessagesfromusers": "{{PLURAL:$4|You have}} $1 from {{PLURAL:$3|another user|$3 users}} ($2).",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|ꯅꯪꯅꯥ}} $1 ꯗꯒꯤ {{PLURAL:$3|ꯑꯇꯣꯞꯄ ꯁꯤꯖꯤꯟꯅꯔꯤꯕ|$3 ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯁꯤꯡ}} ($2).",
        "youhavenewmessagesmanyusers": "ꯅꯪ $1 ꯂꯩꯔꯦ $2 ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯥ ꯃꯌꯥꯝꯗꯒꯤ ꯫",
        "newmessageslinkplural": "{{PLURAL:$1|ꯑꯅꯧꯕ ꯄꯥꯎꯖꯦꯜ ꯱|꯹꯹꯹=ꯑꯅꯧꯕ ꯄꯥꯎꯖꯦꯜꯁꯤꯡ}}",
        "newmessagesdifflinkplural": "ꯑꯔꯣꯏꯕꯥ {{PLURAL:$1|ꯑꯍꯣꯡꯕ|꯹꯹꯹=ꯑꯍꯣꯡꯕꯁꯤꯡ}}",
        "showdiff": "ꯑꯍꯣꯡꯕꯗꯨ ꯎꯨꯠꯂꯨ",
        "blankarticle": "<strong>Warning:</strong> The page you are creating is blank.\nIf you click \"$1\" again, the page will be created without any content.",
        "anoneditwarning": "<strong>Warning:</strong> ꯅꯪ ꯃꯅꯨꯡ ꯆꯪꯗꯔꯤ꯬꯬ ꯫ Your IP address will be publicly visible if you make any edits. If you <strong>[$1 log in]</strong> or <strong>[$2 create an account]</strong>, your edits will be attributed to your username, along with other benefits.",
-       "loginreqlink": "Chang Sinba",
+       "blockedtext": "<strong>ꯅꯪꯒꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕ-ꯃꯃꯤꯡ ꯅꯠꯇꯔꯒ ꯑꯥꯏꯄꯤ ꯑꯦꯗꯔꯦꯁ ꯑꯁꯤ ꯊꯤꯡꯖꯤꯟꯈꯔꯦ ꯫ </strong>\n\nꯑꯊꯤꯡꯕ ꯑꯁꯤ $1ꯅꯥ ꯁꯦꯝꯕꯅꯤ ꯫\nꯃꯔꯝꯗꯨ ꯃꯁꯤꯗ ꯄꯤꯔꯦ<em>$2</em>.\n\n* ꯊꯤꯡꯕ ꯍꯧꯕ: $8\n* ꯊꯤꯡꯕ ꯂꯣꯏꯕ ꯃꯇꯝ: $6\n* ꯑꯇꯝꯅꯅ ꯊꯤꯡꯕ: $7\n\nꯅꯪꯅꯥ $1 ꯄꯥꯎ ꯐꯥꯎꯅꯕ ꯌꯥꯅꯤ ꯅꯠꯇꯔꯒ ꯑꯇꯣꯞꯄ  [[{{MediaWiki:Grouppage-sysop}}|ꯆꯨꯞꯂꯤꯄꯥꯏꯔꯤꯕꯁꯤꯡ]]ꯗ ꯑꯊꯤꯡꯕꯗꯨꯒꯤ ꯃꯇꯥꯡꯗ ꯈꯟꯅ ꯅꯩꯅꯕ ꯫\nꯅꯪꯅꯥ ꯁꯤꯖꯤꯟꯅꯕ ꯌꯥꯔꯣꯏ \"{{int:emailuser}}\" ꯅꯪꯒꯤ ꯑꯦꯀꯥꯎꯟꯗꯨꯒꯤ ꯏꯃꯦꯜ ꯑꯦꯗꯔꯦꯁ ꯑꯃꯥ ꯍꯥꯞꯇ꯭ꯔꯤꯈꯩ [[Special:Preferences|ꯑꯦꯀꯥꯎꯟ ꯀꯔꯝꯕꯗ ꯈꯟꯒꯅꯤ]] ꯑꯃꯁꯨꯡ ꯃꯗꯨꯗꯗꯤ ꯁꯤꯖꯤꯟꯅꯕꯗ ꯊꯤꯡꯗꯦ ꯫\nꯅꯪꯒꯤ ꯍꯧꯖꯤꯛ ꯑꯥꯏꯄꯤ ꯑꯦꯗꯔꯦꯁ ꯁꯤ $3ꯅꯤ, ꯑꯃꯁꯨꯡ ꯊꯤꯡꯈꯤꯕ ꯑꯥꯏꯗꯤ ꯅꯥ #$5ꯅꯤ ꯫\nꯆꯥꯟꯕꯤꯗꯨꯅꯥ ꯃꯊꯛꯀꯤ ꯑꯀꯨꯞꯄ ꯃꯔꯣꯜꯁꯤ ꯌꯥꯎꯍꯟꯂꯨ ꯅꯪꯅꯥ ꯆꯪꯕ ꯈꯨꯗꯤꯡꯗ ꯫",
+       "loginreqlink": "ꯆꯪꯕ",
        "accmailtitle": "ꯄꯥꯁꯋ꯭ꯔꯇ ꯊꯥꯕ",
        "newarticle": "(ꯑꯅꯧꯕꯥ)",
        "newarticletext": "You have followed a link to a page that does not exist yet.\nTo create the page, start typing in the box below (see the [$1 help page] for more info).\nIf you are here by mistake, click your browser's <strong>ꯍꯟꯕ</strong> button.",
        "noarticletext-nopermission": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages, or <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>, but you do not have permission to create this page.",
        "missing-revision": "The revision #$1 of the page named \"{{FULLPAGENAME}}\" does not exist.\n\nThis is usually caused by following an outdated history link to a page that has been deleted.\nDetails can be found in the [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].",
        "userpage-userdoesnotexist-view": "$1 ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯑꯦꯀꯥꯎꯅ ꯁꯤ ꯃꯤꯡ ꯆꯟꯗꯔꯤ ꯫",
+       "clearyourcache": "<strong>ꯏꯁꯤꯟꯒꯗꯕ:</strong> ꯇꯨꯡꯁꯤꯟꯂꯕ ꯃꯇꯨꯡ, ꯅꯪꯅꯥ ꯑꯍꯣꯡꯕ ꯎꯅꯕ ꯅꯪꯒꯤ ꯕꯔꯥꯎꯁꯔ ꯀꯥꯆꯦ ꯕꯥꯏꯄꯥꯁ ꯇꯧꯔꯣ ꯫\n* <strong>ꯐꯥꯌꯥꯔꯐꯣꯛꯁ / ꯁꯐꯥꯔꯤ:</strong> ꯄꯥꯏꯁꯤꯟꯕ<em>ꯊꯥꯡꯇꯣꯛꯄ</em> ꯅꯝꯃꯤꯉꯩ ꯃꯅꯨꯡꯗ<em>ꯑꯃꯨꯛꯍꯟꯅ-ꯆꯤꯡꯉꯣ</em>, ꯅꯠꯇꯔꯒ ꯅꯝꯃꯣ <em>Ctrl-F5</em> ꯑꯃꯥ ꯍꯦꯛꯇꯥ ꯅꯠꯃꯣ <em>Ctrl-R</em> (<em>⌘-R</em> ꯃꯦꯛ ꯱ ꯇꯥ)\n* <strong>ꯒꯨꯒꯜ ꯀꯔꯣꯝ:</strong> ꯅꯝꯃꯣ <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> ꯃꯦꯛ ꯱ ꯗ)\n* <strong>ꯏꯟꯇꯔꯅꯦꯠ ꯑꯦꯛꯁꯄ꯭ꯂꯣꯔꯔ:</strong> ꯄꯥꯏꯁꯤꯟꯕ <em>Ctrl</em> ꯅꯝꯃꯤꯉꯩ ꯃꯅꯨꯡꯗ <em>ꯇꯦꯈꯠꯍꯟꯂꯨ</em>, ꯅꯠꯇꯔꯒr ꯅꯝꯃꯨ <em>Ctrl-F5</em>\n* <strong>ꯑꯣꯄꯦꯔꯥ:</strong> ꯑꯗꯨꯗ ꯆꯠꯂꯨ <em>ꯃꯤꯅꯨ → ꯁꯦꯝꯐꯝ</em> (<em>ꯑꯣꯄꯦꯔꯥ → ꯀꯔꯝꯕꯗ ꯄꯤꯒꯅꯤ</em> ꯃꯦꯛ ꯱ ꯗꯥ) ꯑꯃꯁꯨꯡ ꯑꯗꯨꯒꯥ <em>ꯑꯔꯣꯟꯕ ꯱ꯁꯨꯡ ꯉꯥꯛꯁꯦꯟ → ꯕꯔꯥꯎꯁꯔ ꯗꯥꯇꯥ ꯀꯣꯛꯊꯣꯛꯄ → ꯀꯥꯆꯦ ꯃꯤꯔꯦꯜꯁꯤꯡ ꯑꯃꯁꯨꯡ ꯐꯥꯏꯜꯁꯤꯡ</em> ꯗꯥ ꯫",
        "updated": "(ꯅꯧꯊꯣꯛꯍꯟꯂꯦ)",
        "note": "<strong>ꯏꯁꯤꯟꯒꯗꯕ:</strong>",
        "continue-editing": "ꯁꯦꯝꯒꯠꯄꯒꯤ ꯃꯐꯝꯗꯨꯗꯥ ꯆꯠꯂꯨ",
        "yourdiff": "ꯈꯦꯠꯅꯕꯥꯁꯤꯡ",
        "copyrightwarning": "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).\nIf you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />\nYou are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.\n<strong>Do not submit copyrighted work without permission!</strong>",
        "templatesused": "ꯃꯁꯤꯒꯤ ꯂꯃꯥꯏꯁꯤꯗ ꯁꯤꯖꯤꯟꯅꯕ {{PLURAL:$1|ꯇꯦꯝꯄꯂꯦꯠ|ꯇꯦꯝꯄꯂꯦꯠꯁꯤꯡ}}:",
-       "templatesusedpreview": "{{PLURAL:$1|ꯇꯦꯝꯄꯂꯦꯠ|ꯇꯦꯝꯄꯂꯦꯠꯁꯤꯡ}} ꯄꯔꯤꯕꯤꯌꯨ ꯗ ꯁꯤꯖꯤꯟꯅꯕ:",
+       "templatesusedpreview": "{{PLURAL:$1|ê¯\87ꯦê¯\9dê¯\84ê¯\82ꯦꯠ|ê¯\87ꯦê¯\9dê¯\84ê¯\82ꯦꯠê¯\81ꯤꯡ}} ê¯\83ê¯\81ꯤê¯\92ꯤ ê¯\84ê¯\94ꯤê¯\95ꯤê¯\8cꯨ ê¯\97 ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\95:",
        "template-protected": "(ꯉꯥꯛꯊꯣꯛꯂꯕꯥ)",
        "template-semiprotected": "(ꯇꯪꯈꯥꯏ ꯉꯥꯛꯊꯣꯛꯂꯕꯥ)",
        "hiddencategories": "This page is a member of {{PLURAL:$1|1 hidden category|$1 hidden categories}}:",
        "histlast": "ꯑꯅꯧꯕꯥ",
        "historyempty": "(ꯑꯍꯥꯡꯕ)",
        "history-feed-title": "ꯄꯨꯋꯥꯔꯤ ꯑꯃꯨꯛ ꯍꯟꯅ ꯌꯦꯡꯕ",
+       "history-feed-description": "ꯋꯤꯀꯤꯗ ꯃꯁꯤꯒꯤ ꯂꯃꯥꯏꯁꯤꯒꯤ ꯑꯃꯨꯛꯍꯟꯅ-ꯌꯦꯡꯕ ꯄꯨꯋꯥꯔꯤ",
        "history-feed-item-nocomment": "$2 ꯗ$1",
        "rev-delundel": "ꯑꯍꯣꯡꯕꯥ ꯎꯍꯟꯂꯤꯕꯥ",
        "rev-showdeleted": "ꯎꯨꯠꯂꯨ",
        "nextn": "ꯃꯥꯊꯪ{{PLURAL:$1|$1}}",
        "prev-page": "ꯃꯃꯥꯡꯒꯤ ꯂꯃꯥꯏ",
        "next-page": "ꯃꯊꯪ ꯂꯃꯥꯏ",
-       "prevn-title": "ꯃꯃꯥꯡꯒꯤ $1 {{PLURAL:$1|result|results}}",
+       "prevn-title": "ꯃꯃꯥꯡꯒꯤ $1 {{PLURAL:$1|ꯑꯣꯏꯅꯥ ꯐꯪꯕ|ꯑꯣꯏꯅꯥ ꯐꯪꯉꯦ}}",
        "nextn-title": "ꯃꯊꯪ $1 {{PLURAL:$1|ꯐꯣꯜ|ꯐꯣꯜꯁꯤꯡ}}",
        "shown-title": "ꯎꯠꯂꯨ $1 {{PLURAL:$1|result|results}} ꯂꯃꯥꯏ ꯑꯃꯝ ꯑꯃꯝꯒꯤ ꯑꯣꯏꯅꯥ",
        "viewprevnext": "ꯎꯨꯇꯂꯨ ($1 {{int:pipe-separator}} $2) ($3)",
        "search-relatedarticle": "ꯃꯔꯤꯂꯩꯅꯔꯦ",
        "searchrelated": "ꯃꯔꯤꯂꯩꯅꯔꯦ",
        "searchall": "ꯄꯨꯂꯞ",
-       "search-showingresults": "{{PLURAL:$4|Result <strong>$1</strong> of <strong>$3</strong>|Results <strong>$1 – $2</strong> of <strong>$3</strong>}}",
+       "search-showingresults": "{{PLURAL:$4| ꯑꯣꯏꯅꯥ ꯐꯪꯕ <strong>$1</strong> ꯒꯤ <strong>$3</strong>|ꯑꯣꯏꯅꯥ ꯐꯪꯕꯁꯤꯡ<strong>$1 – $2</strong> ꯒꯤ <strong>$3</strong>}}",
        "search-nonefound": "ꯃꯁꯤꯒꯤ ꯐꯣꯜꯁꯤꯒꯥ ꯆꯥꯟꯅꯕꯥ ꯂꯩꯇꯦ ꯫",
        "powersearch-legend": "ꯈꯨꯃꯥꯡ ꯆꯥꯎꯁꯤꯟꯅ ꯊꯤꯕꯥ",
        "powersearch-togglelabel": "ꯑꯁꯣꯏ ꯑꯔꯥꯟ ꯌꯥꯎꯕꯔ ꯌꯦꯡꯕ:",
        "right-read": "ꯂꯃꯥꯏꯁꯤꯡ ꯄꯥꯕꯥ",
        "right-edit": "ꯂꯃꯥꯏꯁꯤꯡ ꯁꯦꯝꯒꯠꯄ",
        "right-writeapi": "API sijinaduna eba",
-       "newuserlogpage": "ꯁꯤꯖꯤꯅꯅꯔꯤꯕ creation log",
+       "newuserlogpage": "ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯆꯪꯕꯗꯨ ꯁꯥꯕ",
+       "rightslog": "ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯆꯪꯕ ꯍꯛꯁꯤꯡ",
        "action-edit": "ꯃꯁꯤꯒꯤ ꯂꯃꯥꯏꯁꯤ ꯁꯦꯝꯒꯠꯂꯨ",
        "action-createaccount": "ꯃꯁꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯑꯦꯀꯥꯎꯟ ꯁꯤ ꯁꯦꯝꯃꯨ",
        "enhancedrc-history": "ꯄꯨꯋꯥꯔꯤ",
        "recentchanges-label-unpatrolled": "ꯃꯁꯤꯒꯤ ꯁꯦꯝꯒꯠꯄꯁꯤ ꯍꯧꯖꯤꯛꯐꯥꯎ ꯌꯦꯡꯁꯤꯟꯗ꯭ꯔꯤ",
        "recentchanges-label-plusminus": "ꯕꯥꯏꯠꯀꯤ ꯑꯍꯣꯡꯕꯒꯤ ꯃꯇꯪ ꯏꯟꯅꯥ ꯂꯥꯃꯥꯏꯁꯤꯒꯤ ꯑꯆꯧꯕꯥ ꯂꯦꯞꯄꯤ",
        "recentchanges-legend-heading": "<strong>ꯊꯥꯏꯅꯗꯒꯤ</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (also see [[Special:NewPages|ꯑꯅꯧꯕ ꯂꯃꯥꯏꯁꯤꯡ ꯄꯔꯤꯡ]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ꯁꯤꯁꯨ ꯌꯦꯡꯉꯨ [[Special:NewPages|ꯑꯅꯧꯕ ꯂꯃꯥꯏꯁꯤꯡ ꯄꯔꯤꯡ]])",
        "rcnotefrom": "ꯃꯈꯥ {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfrom": "$2$3 ꯁꯤꯗꯒꯤ ꯍꯧꯔꯒꯥ ꯑꯅꯧꯕꯥ ꯑꯍꯣꯡꯕꯗꯨ ꯎꯨꯇꯂꯨ",
        "rcshowhideminor": "$1 ꯄꯤꯛꯅꯥ ꯁꯦꯝꯒꯠꯄꯁꯤꯡ",
        "filehist-comment": "ꯑꯄꯥꯝꯕꯥ ꯐꯣꯡꯗꯣꯛ ꯎ",
        "imagelinks": "ꯐꯥꯏꯜꯒꯤ ꯁꯤꯖꯤꯟꯅꯐꯝ",
        "linkstoimage": "ꯃꯇꯨꯡ ꯏꯟꯕ {{PLURAL:$1|ꯂꯃꯥꯏꯁꯤꯖꯤꯟꯅꯕ|$1ꯂꯃꯥꯏ ꯁꯤꯖꯤꯟꯅꯕ}} ꯃꯁꯤꯒꯤ ꯐꯥꯏꯜ:",
-       "linkstoimage-more": "$1 ê¯\97ê¯\92ꯤ ê¯\8dꯦê¯\9fê¯\85 {{PLURAL:$1|ê¯\82ê¯\83ꯥê¯\8f ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\90ê¯\9d|page use}} ê¯\83ê¯\81ꯤ ê¯\90ꯥê¯\8fê¯\9c ê¯«\nThe following list shows the {{PLURAL:$1|ê¯\91ê¯\8dꯥê¯\9fê¯\95 ê¯\82ê¯\83ꯥê¯\8f|first $1 pages}} that use this file only.\nA [[Special:WhatLinksHere/$2|ꯄꯔꯤꯡ ꯄꯨꯂꯞ]] ꯁꯤ ꯐꯪꯉꯦ ꯫",
+       "linkstoimage-more": "$1 ê¯\97ê¯\92ꯤ ê¯\8dꯦê¯\9fê¯\85 {{PLURAL:$1|ê¯\82ê¯\83ꯥê¯\8f ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\95ê¯\81ꯤꯡ|ê¯\82ê¯\83ꯥê¯\8f ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\95}} ê¯\83ê¯\81ꯤê¯\92ꯤ ê¯\90ꯥê¯\8fê¯\9cê¯\81ꯤê¯\97 ê¯«\nê¯\83ê¯\88ꯥê¯\92ꯤê¯\81ꯤê¯\85ꯥ ê¯\84ê¯\94ꯤꯡê¯\81ꯤê¯\85ꯥ ê¯\8eꯠê¯\82ꯤê¯\95ê¯\81ꯤ {{PLURAL:$1|ê¯\91ê¯\8dꯥê¯\9fê¯\95 ê¯\82ê¯\83ꯥê¯\8f|ê¯\91ê¯\8dꯥê¯\9fê¯\95 $1 ê¯\82ê¯\83ꯥê¯\8fê¯\81ꯤꯡ}} ê¯\90ꯥê¯\8fê¯\9c ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\95 ê¯\88ꯧê¯\87ê¯\85ꯤ ê¯«\n[[Special:WhatLinksHere/$2|ꯄꯔꯤꯡ ꯄꯨꯂꯞ]] ꯁꯤ ꯐꯪꯉꯦ ꯫",
        "nolinkstoimage": "ꯃꯁꯤꯒꯤ ꯐꯥꯏꯜ ꯁꯤ ꯁꯤꯖꯤꯟꯅꯕ ꯂꯃꯥꯏꯁꯤꯡ ꯂꯩꯇꯦ ꯫",
        "linkstoimage-redirect": "$1 (ꯐꯥꯏꯜ ꯱ꯗꯒꯤ ꯱ ꯗ ꯂꯥꯛꯍꯟꯕ) $2",
        "sharedupload-desc-here": "ꯃꯁꯤꯒꯤ ꯐꯥꯏꯜ ꯑꯁꯤ  $1 ꯗꯒꯤꯅꯤ ꯑꯃꯁꯨꯡ ꯑꯇꯩ ꯊꯧꯔꯥꯁꯁꯤꯡꯅꯥ ꯁꯤꯖꯤꯟꯅꯩ ꯫ ꯃꯁꯤꯗ ꯁꯟꯗꯣꯛꯅꯥ ꯇꯥꯛꯄ ꯑꯁꯤ  [$2 ꯐꯥꯏꯜ ꯁꯟꯗꯣꯛꯅꯥ ꯍꯥꯏꯕ ꯂꯃꯥꯏ] ꯃꯈꯥꯒꯤ ꯁꯤꯗ ꯎꯨꯠꯂꯦ ꯫",
        "booksources-search": "ꯊꯤꯕꯥ",
        "specialloguserlabel": "ꯄꯥꯡꯊꯣꯛꯂꯤꯕ ꯃꯤ",
        "log": "ꯆꯪꯕꯥ",
+       "logempty": "ꯃꯁꯤꯒ ꯆꯥꯟꯅꯕ ꯄꯣꯠꯂꯝꯁꯤꯡ ꯆꯪꯗꯦ",
        "allpages": "ꯂꯃꯥꯏꯁꯤꯡ ꯂꯣꯏꯅꯥ",
        "allarticles": "ꯂꯃꯥꯏꯁꯤꯡ ꯂꯣꯏꯅꯥ",
        "allpagessubmit": "ꯆꯠꯂꯨ",
        "allpages-hide-redirects": "ꯃꯥꯏꯀꯩ ꯄꯤꯔꯧꯄꯗꯨ ꯂꯣꯌꯂꯨ",
        "categories": "ꯃꯊꯪ ꯃꯅꯥꯎ ꯈꯥꯏꯗꯣꯛꯄꯥ",
+       "listgrouprights-members": "(ꯊꯧꯃꯤꯁꯤꯡ ꯄꯔꯤꯡ)",
        "emailuser": "ꯃꯁꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯁꯤ ꯏ-ꯃꯦꯜ ꯊꯥꯖꯤꯟꯂꯨ",
        "watchlist": "ꯌꯦꯡꯂꯤꯕ ꯄꯥꯥꯔꯦꯡ",
        "mywatchlist": "ꯌꯦꯡꯅꯕ ꯄꯔꯤꯡ",
        "namespace": "ꯃꯥꯃꯤꯡꯒꯤ ꯃꯐꯝ:",
        "invert": "ꯈꯟꯂꯤꯕꯗꯨ ꯃꯀꯣꯛꯇꯒꯤ ꯂꯥꯛꯍꯟꯕ",
        "tooltip-invert": "Akhannaba maming gi manungda page tungi ahongba lotnaba oopu du yeng ngoo",
-       "namespace_association": "Maming eefam ga marileinaba",
+       "namespace_association": "ꯃꯃꯤꯡ ꯏꯐꯝꯒ ꯃꯔꯤꯂꯩꯅꯕ",
        "tooltip-namespace_association": "Oopu du yengoo maming eefam gi hiramga mari leinaba khangatlaba maming eefam amadi wa ngangfam manung channaba",
        "blanknamespace": "(ꯃꯔꯨꯑꯣꯏꯕ)",
        "contributions": "{{GENDER:$1|ꯁꯤꯖꯤꯟꯅꯔꯤꯕ}} ꯈꯣꯝꯒꯠꯂꯛꯄꯁꯤꯡ",
        "tooltip-t-recentchangeslinked": "ꯃꯁꯤꯒꯤ ꯂꯃꯥꯏꯁꯤꯒꯥ ꯃꯔꯤ ꯂꯩꯅꯕꯥ ꯍꯧꯖꯤꯛꯀꯤ ꯑꯍꯣꯡꯕꯥ ꯂꯥꯃꯥꯏꯁꯤꯡ",
        "tooltip-feed-atom": "ꯂꯃꯥꯏꯁꯤꯒꯤ ꯃꯁꯥ ꯃꯇꯣꯝꯇꯥ ꯌꯣꯛꯈꯠꯂꯛꯄꯥ",
        "tooltip-t-contributions": " {{GENDER:$1|ꯃꯁꯤꯒꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕ}} ꯑꯁꯤ ꯅꯥ ꯈꯣꯝꯖꯤꯟꯂꯛꯂꯤꯕꯥ ꯄꯥꯔꯦꯡ ꯱",
+       "tooltip-t-emailuser": "ꯏꯃꯦꯜ ꯱ ꯊꯥꯎ {{GENDER:$1|ꯃꯁꯤꯒꯤ ꯁꯤꯖꯤꯟꯔꯤꯕ}}ꯁꯤꯗ",
        "tooltip-t-upload": "ꯐꯥꯏꯜꯁꯤꯡ ꯊꯥꯒꯠꯂꯨ",
        "tooltip-t-specialpages": "ꯑꯈꯟꯅꯕ ꯂꯥꯃꯥꯏꯁꯤꯡꯒꯤ ꯄꯥꯔꯦꯡ ꯱",
        "tooltip-t-print": "ꯅꯝꯕ ꯌꯥꯕ ꯃꯑꯣꯡꯒꯤ ꯂꯃꯥꯏ",
-       "tooltip-t-permalink": "Amuk han na yengba lamaisigi Lengdaba Samnafam",
+       "tooltip-t-permalink": "ꯃꯃꯨꯛ ꯍꯟꯅ ꯌꯦꯡꯕ ꯂꯃꯥꯏꯒꯤ ꯂꯦꯡꯗꯕ ꯁꯝꯅꯐꯝ",
        "tooltip-ca-nstab-main": "ꯂꯃꯥꯏꯁꯤꯒꯤ ꯑꯌꯥꯎꯕꯁꯤꯡꯗꯨ ꯎꯨꯇꯂꯨ",
        "tooltip-ca-nstab-user": "ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯥ ꯂꯥꯃꯥꯏꯁꯤ ꯌꯦꯡꯕꯥ",
        "tooltip-ca-nstab-special": "ꯃꯁꯤ ꯑꯈꯟꯅꯕꯥ ꯂꯃꯥꯏꯅꯤ, ꯁꯦꯝꯒꯠꯄꯥ ꯌꯥꯔꯣꯏ",
        "tooltip-ca-nstab-mediawiki": "ꯊꯧꯁꯤꯜꯒꯤ ꯑꯣꯏꯕ ꯄꯥꯎꯖꯦꯜꯗꯨ ꯎꯨꯠꯂꯨ",
        "tooltip-ca-nstab-template": "ꯇꯦꯝꯄꯂꯦꯠ ꯇꯨ ꯎꯨꯠꯂꯨ",
        "tooltip-ca-nstab-category": "ꯃꯆꯥꯈꯥꯏꯕ ꯂꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
+       "tooltip-minoredit": "ꯃꯁꯤꯗꯤ ꯑꯄꯤꯛꯄ ꯁꯦꯝꯒꯠꯄꯅꯤ",
        "tooltip-save": "ꯅꯪꯒꯤ ꯑꯍꯣꯡꯕꯗꯨ ꯇꯨꯡꯁꯤꯟꯂꯨ",
        "tooltip-preview": "ꯅꯪꯒꯤ ꯑꯍꯣꯡꯕꯗꯨ ꯑꯃꯨꯛ ꯍꯟꯅꯥ ꯎꯠꯂꯨ. ꯆꯥꯟꯕꯤꯗꯨꯅꯥ ꯃꯁꯤ ꯍꯥꯟꯅꯥ ꯁꯤꯖꯤꯅꯧ ꯇꯪꯨꯁꯤꯟꯗ꯭ꯔꯤꯉꯧꯗꯥ ꯫",
        "tooltip-diff": "ꯅꯪꯅꯥ ꯏꯔꯤꯕꯥ ꯄꯥꯔꯦꯡꯗꯨꯗꯥ ꯑꯍꯣꯡꯕꯥ ꯎꯠꯂꯨ",
        "pageinfo-lasttime": "ꯅꯧꯔꯤꯕ ꯁꯦꯝꯒꯠꯄꯒꯤ ꯆꯩꯆꯠ",
        "pageinfo-edits": "ꯑꯄꯨꯟꯕ ꯁꯦꯝꯒꯠꯄꯒꯤ ꯃꯁꯤꯡ",
        "pageinfo-authors": "ꯑꯄꯨꯟꯕ ꯑꯈꯟꯅꯕ ꯑꯌꯤꯕꯁꯤꯡꯒꯤ ꯃꯁꯤꯡ",
+       "pageinfo-recent-edits": "ꯀꯨꯏꯗꯔꯤꯕ ꯁꯦꯝꯒꯠꯄ ꯃꯁꯤꯡ($1 ꯐꯥꯎ ꯃꯅꯨꯡꯗ)",
        "pageinfo-magic-words": "ꯃꯦꯖꯤꯛ {{PLURAL:$1|ꯋꯥꯍꯩ|ꯋꯥꯍꯩꯁꯤꯡ}} ($1)",
        "pageinfo-hidden-categories": "ꯂꯣꯠꯍꯟꯕ {{PLURAL:$1|category|ꯃꯆꯥꯛꯈꯥꯏꯕ}} ($1)",
        "pageinfo-templates": "ꯇ꯭ꯔꯥꯟꯁꯀꯂꯨꯗꯦꯗ {{PLURAL:$1|ꯇꯦꯝꯄꯂꯦꯠ|ꯇꯦꯝꯄꯂꯦꯠꯁꯤꯡ}} ($1)",
index b6fa2af..cd64fb3 100644 (file)
        "blocklist-timestamp": "အချိန်တံဆိပ်",
        "blocklist-target": "ပစ်မှတ်",
        "blocklist-expiry": "သက်တမ်းကုန်လွန်မည်",
-       "blocklist-by": "á\80\95á\80\90á\80ºá\80\95á\80\84á\80ºá\80\91á\80¬á\80¸á\80\9eá\80\8aá\80·á\80º á\80¡á\80\80á\80ºá\80\92á\80\99á\80­á\80\94á\80º",
+       "blocklist-by": "á\80\95á\80­á\80\90á\80ºá\80\95á\80\84á\80ºá\80\91á\80¬á\80¸á\80\9eá\80\8aá\80·á\80º á\80\85á\80®á\80\99á\80¶á\80\81á\80\94á\80·á\80ºá\80\81á\80½á\80²á\80\9eá\80°",
        "blocklist-params": "ပိတ်ပင်မှု ပါရာမီတာများ",
        "blocklist-reason": "အကြောင်းပြချက်",
        "ipblocklist-submit": "ရှာဖွေရန်",
index dd6aa2e..3365b11 100644 (file)
        "redirectedfrom": "(Redirect 'a $1)",
        "redirectpagesub": "Paggena 'e redirect",
        "redirectto": "Reindirizza a:",
-       "lastmodifiedat": "Sta paggena fuje cagnàta ll'urdema vota 'o $1, 'e $2.",
+       "lastmodifiedat": "Sta paggena venette cagnàta ll'urdema vota 'o $1, 'e $2.",
        "viewcount": "Chesta paggena è stata liggiùta {{PLURAL:$1|una vòta|$1 vòte}}.",
        "protectedpage": "Paggena prutetta",
        "jumpto": "Vaje a:",
        "externaldberror": "Ce sta n'errore ch' 'e server d'autenticazione esterno, o pure nun v'è permesso accedere all'aghiurnamento d' 'o cunto sterno vuosto.",
        "login": "Tràse",
        "login-security": "Cunferma l'identità",
-       "nav-login-createaccount": "Trasite o criate n'acciesso nuovo",
+       "nav-login-createaccount": "Trasite o criate n'acciesso novo",
        "logout": "Jèsce",
        "userlogout": "Jèsce",
        "notloggedin": "Acciesso nun affettuato",
-       "userlogin-noaccount": "Nun tenite perzine n'acciesso?",
+       "userlogin-noaccount": "Perzine nun tenite n'acciesso?",
        "userlogin-joinproject": "Facite 'o riggistro ncopp'a {{SITENAME}}",
        "createaccount": "Crèa nu cunto nuovo",
        "userlogin-resetpassword-link": "Te sì scurdat' 'a password?",
        "newarticle": "(Nuovo)",
        "newarticletext": "Site ghiuto/a addò nu link 'e na paggena ca nun esiste ancora.\nPe crià sta paggena, accummenciate a scrivere dint'<nowiki/>'a cascia ccà abbascio (vedite 'a [$1 paggena d'aiuto] pe vedè cchiù 'nfurmazziune).\nSi site venuto/a ccà pe sbaglio, vedite 'e sprémmere 'o buttòne '''Arreto''' d'<nowiki/>o navigatóre.",
        "anontalkpagetext": "----\n''Chest'è 'a paggena 'e discussione 'e n'utente anonimo ca ancora nun s'è fatt' n'utenza o ca nun 'a sta ausanno.''\n\nPe' l'identificà avite 'e truvà 'o nummero d' 'o ndirizzo IP d' 'o sujo. L'indirizze IP se ponno spartì però sempe ausanno cunte differente.\n\nSi site n'utente anonimo e penzate ca 'e cummente ccà dint'a sta paggena nun parlano 'e vuje, allora [[Special:CreateAccount|criate n'utenza nnova]] o [[Special:UserLogin|trasite cu chella ca tenite già]] pe' nun sta' mmescato mmiez'a l'ati utente anonime n futuro.",
-       "noarticletext": "Mo' mo' 'a paggena richiesta è abbacante. Se pò [[Special:Search/{{PAGENAME}}|ascià stu titolo]] dint'a l'ati paggene d' 'o sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ascià dint'e riggistre azzeccate] o pure [{{fullurl:{{FULLPAGENAME}}|action=edit}} crià 'a paggena mo']</span>.",
-       "noarticletext-nopermission": "Mo' mo' 'a paggena richiesta è abbacante. Se pò [[Special:Search/{{PAGENAME}}|ascià stu titolo]] dint'a l'ati paggene d' 'o sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ascià dint'e riggistre azzeccate]</span>, però nun tenite 'o permesso 'a crià sta paggena.",
+       "noarticletext": "Mo mmo 'a paggena richiesta è abbacante. Può [[Special:Search/{{PAGENAME}}|ascià stu titolo]] dint' 'a ll'ati paggene d' 'o sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ascià dint'e riggistre azzeccate] o pure [{{fullurl:{{FULLPAGENAME}}|action=edit}} crià 'a paggena 'a via 'e subbeto]</span>.",
+       "noarticletext-nopermission": "Mo mmo 'a paggena richiesta è abbacante. Se pò [[Special:Search/{{PAGENAME}}|ascià stu titolo]] dint' 'a ll'ati paggene d' 'o sito, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ascià dint'e riggistre azzeccate]</span>, però nun tenite 'o permesso 'e crià sta paggena.",
        "missing-revision": "'A verziona #$1 d' 'a paggena \"{{FULLPAGENAME}}\" nun esiste.\n\nChest'è causato quanno se và dint'a nu link a na paggena ch'è stata scancellata.\n'E dettaglie se ponno truvà dint'a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 'o riggistro 'e scancellamiente].",
        "userpage-userdoesnotexist": "'O cunto utente \"<nowiki>$1</nowiki>\" nun è riggistrato. Cuntrolla ca si buò overo crià o cagnà sta paggena.",
        "userpage-userdoesnotexist-view": "'O cunto utente \"$1\" nun è riggistrato.",
        "blocked-notice-logextract": "St'utente è bloccato mò.\nL'urdemo elemento d' 'o riggistro 'e blocche è ripurtato ccà abbascio p'avé nu riferimento:",
-       "clearyourcache": "<strong>Nota:</strong> aroppo sarvate putisse necessità 'e pulezzà 'a caché d' 'o navigatóre pe' vedé 'e cagnamiente. \n*<strong>Firefox / Safari</strong>: sprémme 'o buttóne maiuscole e ffà clic ncopp'a ''Recarreca'', o pure spremme ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' ncopp'a Mac)\n*<strong>Google Chrome''': spremme ''Ctrl-Shift-R'' (''⌘-Shift-R'' ncopp'a nu Mac)\n*<strong>Internet Explorer</strong>: spremme 'o buttóne ''Ctrl'' pe' tramente ca faie click ncopp'a ''Refresh'', o pure spremmere ''Ctrl-F5''\n* <strong>Opera:</strong> Vaje addò 'o <em>Menu → Mpustaziune</em> (<em>Opera → Mpustaziune</em> ncopp' 'o Mac) e po' ncopp'a <em>Privacy & sicurezza → Pulezza date d' 'o browser → Immaggene e file d' 'a cache</em>.",
+       "clearyourcache": "<strong>Notarella:</strong> aroppo sarvate putisse necessità 'e pulezzà 'a caché d' 'o navigatóre pe vedé 'e cagnamienti. \n*<strong>Firefox / Safari</strong>: sprémme 'o buttóne maiuscole e ffà clic ncopp'a ''Recarreca'', o pure spremme ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' ncopp'a Mac)\n*<strong>Google Chrome''': spremme ''Ctrl-Shift-R'' (''⌘-Shift-R'' ncopp'a nu Mac)\n*<strong>Internet Explorer</strong>: spremme 'o buttóne ''Ctrl'' pe' tramente ca faie click ncopp'a ''Refresh'', o pure spremmere ''Ctrl-F5''\n* <strong>Opera:</strong> Vaje addò 'o <em>Menu → Mpustaziune</em> (<em>Opera → Mpustaziune</em> ncopp' 'o Mac) e po' ncopp'a <em>Privacy & sicurezza → Pulezza date d' 'o browser → Immaggene e file d' 'a cache</em>.",
        "usercssyoucanpreview": "'''Cunziglio:''' spremme 'o buttone 'Vide anteprimma' pe' pruvà 'o CSS nuovo apprimma d' 'o sarvà.",
        "userjsonyoucanpreview": "<strong>Cunziglio:</strong> premme 'o buttone \"{{int:showpreview}}\" pe' pruvà 'o JSON nuovo apprimma d' 'o sarvà.",
        "userjsyoucanpreview": "'''Cunziglio:''' spremme 'o buttone 'Vide anteprimma' pe' pruvà 'o JavaScript nuovo apprimma d' 'o sarvà.",
        "protect-existing-expiry-infinity": "Tiempo d'ammaturamiento: infinito",
        "protect-otherreason": "Ati/cchiù ragiune:",
        "protect-otherreason-op": "Ati ragiune",
-       "protect-dropdown": "*Mutive 'e prutezione comune\n** Vandalisme eccessive\n** Spam eccessivo\n** 'Uerre 'e cagnamiente controproducente\n** Paggena cu troppo traffeco",
+       "protect-dropdown": "*Mutive 'e prutezione comune\n** Vandalisme eccessive\n** Spam eccessivo\n** 'Uerra 'e cagnamienti controproducente\n** Paggena cu assaje traffeco\n** Paggena 'e sistema ausata assai",
        "protect-edit-reasonlist": "Càgna 'e mutive 'e prutezione",
        "protect-expiry-options": "1 ore:1 hour,1 juorno:1 day,1 semmana:1 week,2 semmane:2 weeks,1 mese:1 month,3 mise:3 months,6 mise:6 months,1 anno:1 year,infinito:infinite",
        "restriction-type": "Permesse:",
        "specialpages-note-restricted": "* Paggene speciale normale.\n* <span class=\"mw-specialpagerestricted\">Paggene speciale ch' 'e restriziune.</span>",
        "specialpages-group-maintenance": "Report 'e manutenzione",
        "specialpages-group-other": "Ati paggene speciale",
-       "specialpages-group-login": "Trasite o criate n'acciesso nuovo",
+       "specialpages-group-login": "Trasite o criate n'acciesso novo",
        "specialpages-group-changes": "Urdeme cagnamiénte e riggistre",
        "specialpages-group-media": "Riepileghe 'e media e carreche",
        "specialpages-group-users": "Utente e deritte",
index c56b4df..4bf2965 100644 (file)
@@ -94,7 +94,7 @@
        "october": "oktober",
        "november": "november",
        "december": "december",
-       "january-gen": "jannewaori",
+       "january-gen": "janri",
        "february-gen": "februåri",
        "march-gen": "meert",
        "april-gen": "april",
        "november-date": "$1 november",
        "december-date": "$1 desember",
        "pagecategories": "{{PLURAL:$1|Kategory|Kategoryen}}",
-       "category_header": "Artikels in kategorie $1",
+       "category_header": "Artikels in kategory $1",
        "subcategories": "Subkategorieën",
        "category-media-header": "Media in kategorie \"$1\"",
        "category-empty": "''In disse kategoria staon op t moment nog gien artikels of media.''",
        "actions": "Haandeling",
        "namespaces": "Naamruumdes",
        "variants": "Varianten",
-       "navigation-heading": "Navigatymenu",
+       "navigation-heading": "Navigatymenü",
        "errorpagetitle": "Foutmelding",
        "returnto": "Weerumme naor $1.",
        "tagline": "Van {{SITENAME}}",
        "permalink": "Vaste verwysing",
        "print": "Aofdrokken",
        "view": "Leasen",
-       "view-foreign": "Bekieken op $1",
+       "view-foreign": "Bekyken up $1",
        "edit": "Bewarken",
        "edit-local": "Lokale beschrieving bewarken",
        "create": "Anmaken",
        "aboutsite": "Oaver {{SITENAME}}",
        "aboutpage": "Project:Info",
        "copyright": "De inhoud is beschikbaor onder de $1 as der niks aanders an-egeven is.",
-       "copyrightpage": "{{ns:project}}:Auteursrechten",
+       "copyrightpage": "{{ns:project}}:Autöörsrechten",
        "currentevents": "In et nys",
        "currentevents-url": "Project:In et nys",
        "disclaimers": "Vöärbehold",
        "youhavenewmessagesmanyusers": "Je hebben $1 van n bulte gebrukers ($2).",
        "newmessageslinkplural": "{{PLURAL:$1|n niej bericht|999=nieje berichten}}",
        "newmessagesdifflinkplural": "leste {{PLURAL:$1|wieziging|999=wiezigingen}}",
-       "youhavenewmessagesmulti": "Jy hebben nye berichten up $1",
+       "youhavenewmessagesmulti": "Jy hebbet nye berichten up $1",
        "editsection": "bewark",
        "editold": "bewark",
        "viewsourceold": "brontekste bekyken",
        "perfcached": "Disse gegevens koemen uut t tussengeheugen en bin misschien niet aktueel. Der {{PLURAL:$1|is hooguut een resultaot|bin hooguut $1 resultaoten}} beschikbaor in t tussengeheugen.",
        "perfcachedts": "Disse gegevens koemen uut t tussengeheugen die veur t lest bie-ewörken is op $2 um $3. Der {{PLURAL:$4|is hooguut een resultaot|bin hooguut $4 resultaoten}} beschikbaor in t tussengeheugen.",
        "querypage-no-updates": "'''Disse zied wördt niet meer bie-ewörken.'''",
-       "viewsource": "Brontekste bekyken",
+       "viewsource": "Brontekst bekyken",
        "viewsource-title": "Bron bekieken van $1",
        "actionthrottled": "Haandeling tegenehöllen",
        "actionthrottledtext": "As maotregel tegen t plaotsen van alderhaande moek, is t antal keren da'j disse haandeling in n korte tied uutvoeren kunnen beteund. Je hebben de limiet overschrejen. Probeer t over n antal minuten weer.",
        "logout": "Afmelden",
        "userlogout": "Aofmelden",
        "notloggedin": "Neet an-emelded",
-       "userlogin-noaccount": "Heb jy noch geen gebrukersname?",
+       "userlogin-noaccount": "Heb jy noch geen brukersname?",
        "userlogin-joinproject": "Wörd lid van {{SITENAME}}",
        "createaccount": "Inskryven",
        "userlogin-resetpassword-link": "Juuw wachtwoord vergeaten?",
        "pt-login": "Anmelden",
        "pt-login-button": "Anmelden",
        "pt-createaccount": "Inskryven",
-       "pt-userlogout": "Ofmelden",
+       "pt-userlogout": "Afmelden",
        "php-mail-error-unknown": "Der was n onbekende fout mit de mail()-funksie van PHP",
        "user-mail-no-addy": "Eprobeerd n berichjen te versturen zonder n netpostadres",
        "user-mail-no-body": "Der is eprobeerd n netbreef zonder tekste of mit n biester korte tekste te versturen.",
        "subject": "Onderwarp:",
        "minoredit": "kleine wysiging",
        "watchthis": "volg disse syde",
-       "savearticle": "Syde upslån",
-       "savechanges": "Wysigingen upslån",
+       "savearticle": "Syde uutgeaven",
+       "savechanges": "Wysigingen uutgeaven",
        "publishpage": "Zied uutbrengen",
-       "publishchanges": "Wysigingen üütbrengen",
+       "publishchanges": "Wysigingen uutgeaven",
        "preview": "Naokieken",
        "showpreview": "Bewarking nåkyken",
        "showdiff": "Verskil bekyken",
        "summary-preview": "Samenvatting naokieken:",
        "subject-preview": "Onderwarp naokieken:",
        "blockedtitle": "Gebruker is eblokkeerd",
-       "blockedtext": "'''Joew gebrukersnaam of IP-adres is eblokkeerd.'''\n\nJe bin eblokkeerd deur: $1.\nDe op-egeven reden is: ''$2''.\n\n* Eblokkeerd vanaof: $8\n* Eblokkeerd tot: $6\n* Bedoeld um te blokkeren: $7\n\nJe kunnen kontakt opnemen mit $1 of n aandere [[{{MediaWiki:Grouppage-sysop}}|beheerder]] um de blokkering te bepraoten.\nJe kunnen gien gebruukmaken van de funksie 'een bericht sturen', behalven a'j n geldig netpostadres op-egeven hebben in joew [[Special:Preferences|veurkeuren]] en t gebruuk van disse funksie niet eblokkeerd is.\nt IP-adres da'j noen gebruken is $3 en t blokkeringsnummer is #$5.\nVermeld t allebeie a'j argens op disse blokkering reageren.",
+       "blockedtext": "<strong>Juw brukersname of IP-adres is blokkeerd.</strong>\n\nJy binnet blokkeerd döär $1.\nDe upgeaven readen is <em>$2</em>.\n\n* Blokkeerd vanaf: $8\n* Blokkeerd tot: $6\n* Bedoold üm te blokkeren: $7\n\nJy künnet kontakt upneamen mid $1 of een andere [[{{MediaWiki:Grouppage-sysop}}|beheyrder]] üm de blokkering te bepråten.\nJy künnet de funkty \"{{int:emailuser}}\" neet bruken, behalven as jy een geldig e-postadres in juw [[Special:Preferences|instellingen]] upgeaven hebbet en et gebruuk van disse funkty neet blokkeerd is.\nEt IP-adres wat jy nu bruket is $3, en et blokkeringsnummer is #$5.\nVermeld de gegeavens dee hyrboaven stån as jy argens up disse blokkering reageren.",
        "autoblockedtext": "Joew IP-adres is automaties eblokkeerd umdat t gebruukt wördt deur n aandere gebruker, die eblokkeerd wördt deur $1.\nDe reden hierveur was:\n\n:''$2''\n\n* Begint: $8\n* Löp of nao: $6\n* Wee eblokkeerd wördt: $7\n\nJe kunnen kontakt opnemen mit $1 of n van de aandere\n[[{{MediaWiki:Grouppage-sysop}}|beheerders]] um de blokkering te bepraoten.\n\nNB: je kunnen de opsie \"n bericht sturen\" niet gebruken, behalven a'j n geldig netpostadres op-egeven hebben in de [[Special:Preferences|gebrukersveurkeuren]] en je niet eblokkeerd bin.\n\nJoew IP-adres is $3 en joew blokkeernummer is $5.\nGeef disse nummers deur a'j kontakt mit ene opnemen over de blokkering.",
        "blockednoreason": "gien reden op-egeven",
        "whitelistedittext": "Um ziejen te kunnen wiezigen, mu'j $1 ween",
        "accmailtext": "Der is n willekeurig wachtwoord veur [[User talk:$1|$1]] verstuurd naor $2. t Kan ewiezigd wörden op de zied ''[[Special:ChangePassword|wachtwoord wiezigen]]'' naoda'j an-emeld bin.",
        "newarticle": "(Niej)",
        "newarticletext": "Disse zied besteet nog niet.\nIn t veld hieronder ku'j wat schrieven um disse zied an te maken (meer informasie vie'j op de [$1 hulpzied]).\nA'j hier per ongelok terechtekeumen bin gebruuk dan de knoppe '''veurige''' um weerumme te gaon.",
-       "anontalkpagetext": "---- ''Disse overlegzied heurt bie n anonieme gebruker die nog gien gebrukersnaam hef, of t niet gebruukt. We gebruken daorumme t IP-adres um hum of heur te herkennen, mer t kan oek ween dat meerdere personen t zelfde IP-adres gebruken, en da'j hiermee berichten ontvangen die niet veur joe bedoeld bin. A'j dit veurkoemen willen, dan ku'j t best [[Special:CreateAccount|n gebrukersnaam anmaken]] of [[Special:UserLogin|anmelden]].''",
+       "anontalkpagetext": "----\n<em>Disse oaverlegsyde höyrt by een anonyme bruker dee noch geen brukersname hevt, of et neet bruukt.</em>\nDårümme bruken wy et IP-adresse ter identifikaty. Een IP-adresse kan döär meyrere lüde bruked wörden. As jy een anonyme bruker binnet, en et gevööl hebbet dat jy berichten kryget dee neet vöär ju bedoold binnet [[Special:CreateAccount|skryv ju eigen dan in]] of [[Special:UserLogin|meld ju eigen an]] üm verwarring mid andere anonyme brukers in de tokumst te vöärkommen.''",
        "noarticletext": "Der steyt nun geen tekst up disse syde.\nJy künnet [[Special:Search/{{PAGENAME}}|de titel upsöken]] in andere syden,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söken in de logboken],\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse syde anmaken]</span>.",
        "noarticletext-nopermission": "Op disse zied steet gien tekste.\nJe kunnen [[Special:Search/{{PAGENAME}}|zeuken naor disse term]] in aandere ziejen of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboeken deurzeuken]</span>, mer je hebben gien rechten um disse zied an te maken.",
        "missing-revision": "De versie #$1 van de zied \"{{FULLPAGENAME}} besteet niet.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].",
        "userpage-userdoesnotexist": "Je bewarken n gebrukerszied van n gebruker die niet besteet (gebruker \"<nowiki>$1</nowiki>\"). Kiek effen nao o'j disse zied wel anmaken/bewarken willen.",
        "userpage-userdoesnotexist-view": "Gebruker \"$1\" steet hier niet in-eschreven",
        "blocked-notice-logextract": "Disse gebruker is op t moment eblokkeerd.\nDe leste regel uut t blokkeerlogboek steet hieronder as referensie:",
-       "clearyourcache": "'''Waort je:''' naodat de wiezigingen op-esleugen bin, mut t tussengeheugen van de webkieker nog eleegd wörden um t te kunnen zien. \n*'''Firefox / Safari:''' drok op ''Shift'' terwiel je op ''verniejen'' klikken, of gebruuk ''Ctrl-F5'' of ''Ctrl-R'' (''⌘-R'' op n knipperkiste van Mac)\n* '''Google Chrome:''' drok op ''Ctrl-Shift-R'' (''⌘-Shift-R'' op n knipperkiste van Mac)\n*'''Internet Explorer:''' drok op ''Ctrl'' terwiel je op ''verniejen'' klikken of drok op ''Ctrl-F5''\n*'''Opera:''' leeg t tussengeheugen in ''Extra → Voorkeuren\"",
+       "clearyourcache": "<strong>Upmarking:</strong> nå et seakeren, sül jy lichtkans et tüskengehöägen van juw webkyker mütten leadigen üm de wysigingen te seen.\n* <strong>Firefox / Safari:</strong> hold <em>Shift</em> indrükked terwyl jy up <em>Herladen</em> klikket, of drük up <em>Ctrl-F5</em> of <em>Ctrl-R</em> (<em>⌘-R</em> up een Mac)\n* <strong>Google Chrome:</strong> drük up <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> up een Mac)\n* <strong>Internet Explorer:</strong> hold <em>Ctrl</em> indrükked terwyl jy up <em>Herladen</em> klikket, of drük up <em>Ctrl-F5</em>\n* <strong>Opera:</strong> gå nå <em>Menü → Instellingen</em> (<em>Opera → Vöärköären</em> up een Mac) en dan nå <em>Privaatheid & beveiliging → Söökgeskydenisse wisken → Tydelike afbealdingen en bestanden</em>.",
        "usercssyoucanpreview": "'''Tip:''' gebruuk de knoppe \"{{int:showpreview}}\" um joew nieje css/js nao te kieken veurda'j t opslaon.",
        "userjsyoucanpreview": "'''Tip:''' gebruuk de knoppe \"{{int:showpreview}}\" um joew nieje css/js nao te kieken veurda'j t opslaon.",
        "usercsspreview": "'''Dit is allinnig n naokieksel van joew persoonlike CSS.'''\n'''t Is nog niet op-esleugen!'''",
        "templatesusedpreview": "{{PLURAL:$1|Mal|Mallen}} die in disse bewarking gebruukt wörden:",
        "templatesusedsection": "{{PLURAL:$1|Mal|Mallen}} die in dit subkopjen gebruukt wörden:",
        "template-protected": "(beveiligd)",
-       "template-semiprotected": "(half-beveiligd)",
+       "template-semiprotected": "(halv-beveiligd)",
        "hiddencategories": "Disse zied völt in de volgende verbörgen {{PLURAL:$1|kategorie|kategorieën}}:",
        "edittools": "<!-- Disse tekste steet onder de bewarkings- en bestaandinlaodformulieren. -->",
        "nocreatetext": "Disse webstee hef de meugelikheid um nieje ziejen an te maken beteund. Je kunnen ziejen die al bestaon wiezigen of je kunnen je [[Special:UserLogin|anmelden of n gebrukerszied anmaken]].",
        "editundo": "weaderümmedraien",
        "diff-empty": "(Gien verschil)",
        "diff-multi-sameuser": "({{PLURAL:$1|n Tussenliggende versie|$1 tussenliggende versies}} deur de zelfde gebruker is verbörgen)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär {{PLURAL:$2|eyn andere bruker|$2 brukers}} neet weadergeaven)",
        "diff-multi-manyusers": "($1 tussenliggende {{PLURAL:$1|versie|versies}} deur meer as $2 {{PLURAL:$2|gebruker|gebrukers}} niet weeregeven)",
        "difference-missing-revision": "{{PLURAL:$2|Eén versie|$2 versies}} van disse verschillen ($1) {{PLURAL:$2|is|bin}} niet evunnen.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].",
        "searchresults": "Söökresultaten",
        "prevn-title": "{{PLURAL:$1|Veurig resultaot|Veurige $1 resultaoten}}",
        "nextn-title": "{{PLURAL:$1|Volgend resultaot|Volgende $1 resultaoten}}",
        "shown-title": "Låt $1 {{PLURAL:$1|resultaat|resultaten}} per syde seen",
-       "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3)",
+       "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) bekyken.",
        "searchmenu-exists": "'''Der is n zied mit de naam \"[[:$1]]\" op disse wiki.'''",
        "searchmenu-new": "<strong>De zied \"[[:$1]]\" op disse wiki anmaken!</strong> \n{{PLURAL:$2|0=|Zie oek de zied mit joew zeukresultaoten.|Zie oek de lieste mit evunnen zeukresultaoten.}}",
        "searchprofile-articles": "Artikels",
        "searchprofile-advanced-tooltip": "Söken in de angeaven naamruumden",
        "search-result-size": "$1 ({{PLURAL:$2|1 woord|$2 woorden}})",
        "search-result-category-size": "{{PLURAL:$1|1 kategorielid|$1 kategorielejen}} ({{PLURAL:$2|1 onderkategorie|$2 onderkategorieën}}, {{PLURAL:$3|1 bestaand|$3 bestaanden}})",
-       "search-redirect": "(deurverwiezing vanaof $1)",
+       "search-redirect": "(döärverwysing vanaf $1)",
        "search-section": "(onderwarp $1)",
+       "search-file-match": "(kümt oavereyne mid de inhold van et bestand)",
        "search-suggest": "Bedoelden je: $1",
        "search-interwiki-caption": "Zusterprojekten",
        "search-interwiki-default": "Resultaoten van $1:",
        "searchall": "alles",
        "showingresults": "Hieronder {{PLURAL:$1|steet '''1''' resultaot|staon '''$1''' resultaoten}}  <b>$1</b> vanaof nummer <b>$2</b>.",
        "search-showingresults": "{{PLURAL:$4|Resultaot <strong>$1</strong> van <strong>$2</strong>|Resultaoten <strong>$1 - $2</strong> van <strong>$3</strong>}}",
-       "search-nonefound": "Der bin gien resultaoten veur de zeukopdrachte.",
+       "search-nonefound": "Der binnet geen resultaten vöär de söökupdrachte.",
        "powersearch-legend": "Uutgebreid zeuken",
        "powersearch-ns": "Zeuken in naamruumten:",
        "powersearch-togglelabel": "Selekteren:",
        "action-editmyprivateinfo": "joew eigen priveegegevens bewarken",
        "nchanges": "$1 {{PLURAL:$1|wieziging|wiezigingen}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|sinds joew leste bezeuk}}",
-       "enhancedrc-history": "geschiedenisse",
+       "enhancedrc-history": "geskydenisse",
        "recentchanges": "Lätste wysigingen",
        "recentchanges-legend": "Optys vöär lätste wysigingen",
        "recentchanges-summary": "Up disse syde kün jy de lätste wysigingen van disse wiki bekyken.",
        "rcfilters-quickfilters-placeholder-description": "Üm juuw filterinstellingen up te slån en et låter te gebruken, klik up et bladwyserikoon underan by \"Aktive filters\".",
        "rcfilters-savedqueries-apply-label": "Instellingen opslaon",
        "rcfilters-savedqueries-cancel-label": "Aofbreken",
-       "rcfilters-savedqueries-add-new-title": "Filterinstellingen upslån",
+       "rcfilters-savedqueries-add-new-title": "Filterinstellingen seakeren",
        "rcfilters-restore-default-filters": "Standardfilters weerummezetten",
        "rcfilters-clear-all-filters": "Alle filters vortdoon",
        "rcfilters-show-new-changes": "Låt nyste wysigingen seen",
-       "rcfilters-search-placeholder": "Filter wysigingen (gebrüük et menü of söök up filtername)",
+       "rcfilters-search-placeholder": "Filter wysigingen (bruuk et menü of söök up filtername)",
        "rcfilters-filterlist-feedbacklink": "Låt uns weaten wat jy van disse (nye) filterhülpmiddels vinden",
        "rcfilters-highlightbutton-title": "Resultåten markeren",
        "rcfilters-highlightmenu-title": "Kies n kleur",
        "rcfilters-filter-user-experience-level-newcomer-label": "Anwas",
        "rcfilters-filter-user-experience-level-newcomer-description": "An-emelden bewarkers dee minder as 10 bewarkingen edån hebben of 4 dagen aktiv ewesd hebben.",
        "rcfilters-filter-user-experience-level-learner-label": "Leyrlingen",
-       "rcfilters-filter-user-experience-level-learner-description": "An-emelde bewarkers mid meyr ervåring as \"anwas\", mär minder as \"ervåren gebrukers\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Anmeldede bewarkers mid meyr ervaring as \"anwas\", mär minder as \"ervaren brukers\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Ervåren gebrukers",
        "rcfilters-filter-user-experience-level-experienced-description": "An-emelde bewarkers mid meyr as 500 bewarkingen en 30 dagen van aktiviteit.",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-pageedits-label": "Sydbewarkingen",
        "rcfilters-filter-pageedits-description": "Wysigingen an de wiki-inhold, diskussys, kategorybeskryvingen…",
        "rcfilters-filter-newpages-label": "Nye syden",
-       "rcfilters-filter-newpages-description": "Bewarkingen wårmead jy een nye syde anmaken.",
+       "rcfilters-filter-newpages-description": "Bewarkingen wårmead jy een nye syde anmaket.",
        "rcfilters-filter-categorization-label": "Kategorywysigingen",
        "rcfilters-filter-categorization-description": "Upgave van syden dee to-evoogd of vordedån wörden uut kategoryen.",
        "rcfilters-filter-logactions-label": "Eregistreerde aktys",
        "rclistfrom": "Bekyk wysigingen vanaf $3 $2",
        "rcshowhideminor": "$1 kleine wysigingen",
        "rcshowhideminor-show": "Bekiek",
-       "rcshowhideminor-hide": "Verbarg",
+       "rcshowhideminor-hide": "verbargen",
        "rcshowhidebots": "$1 botbrukers",
-       "rcshowhidebots-show": "Bekiek",
+       "rcshowhidebots-show": "bekyken",
        "rcshowhidebots-hide": "Verbarg",
        "rcshowhideliu": "$1 registreerde brukers",
        "rcshowhideliu-show": "Bekiek",
-       "rcshowhideliu-hide": "Verbarg",
-       "rcshowhideanons": "$1 anonime brukers",
+       "rcshowhideliu-hide": "verbargen",
+       "rcshowhideanons": "$1 anonyme brukers",
        "rcshowhideanons-show": "Bekiek",
-       "rcshowhideanons-hide": "Verbarg",
+       "rcshowhideanons-hide": "verbargen",
        "rcshowhidepatr": "$1 nao-ekeken bewarkingen",
        "rcshowhidepatr-show": "Bekiek",
        "rcshowhidepatr-hide": "Verbarg",
        "rcshowhidemine": "$1 myn bewarkingen",
        "rcshowhidemine-show": "Bekiek",
-       "rcshowhidemine-hide": "Verbarg",
+       "rcshowhidemine-hide": "verbargen",
        "rcshowhidecategorization": "$1 kategorisering van ziejen",
        "rcshowhidecategorization-show": "Bekiek",
        "rcshowhidecategorization-hide": "Verbarg",
        "recentchangeslinked-feed": "Volg verwysingen",
        "recentchangeslinked-toolbox": "Volg verwysingen",
        "recentchangeslinked-title": "Wiezigingen verwaant an $1",
-       "recentchangeslinked-summary": "Op disse spesiale zied steet n lieste mit de leste wieziginen op ziejen waornaor verwezen wördt. Ziejen op [[Special:Watchlist|joew volglieste]] staon '''vet'''.",
+       "recentchangeslinked-summary": "Voor een sydname in üm bewarkingen te seen up syden wårnå verweasen wördt of dårnå verwysen. (Voor {{ns:category}}:kategoryname in üm leaden van een kategory te seen). Bewarkingen van syden up [[Special:Watchlist|juw volglyste]] binnet <strong>vetdrükked</strong>.",
        "recentchangeslinked-page": "Ziednaam:",
        "recentchangeslinked-to": "Bekiek wiezigingen op ziejen mit verwiezingen naor disse zied",
        "recentchanges-page-added-to-category": "[[:$1]] bie kategorie ezet",
        "filehist-comment": "Kommentaar",
        "imagelinks": "Bestandsbruuk",
        "linkstoimage": "Dit bestand wördt up de volgende {{PLURAL:$1|syde|$1 syden}} bruked:",
-       "linkstoimage-more": "Der {{PLURAL:$2|is|bin}} meer as $1 {{PLURAL:$1|verwiezing|verwiezingen}} naor dit bestaand.\nDe volgende lieste gif allinnig de eerste {{PLURAL:$1|verwiezing|$1 verwiezingen}} naor dit bestaand weer.\nDe [[Special:WhatLinksHere/$2|hele lieste]] is oek beschikbaor.",
+       "linkstoimage-more": "Meyr as $1 {{PLURAL:$1|syde bruukt|syden bruken}} dit bestand.\nDe volgende lyste givt allinnig de {{PLURAL:$1|eyrste syde|eyrste $1 syden}} weader dee dit bestand bruukt.\nDe [[Special:WhatLinksHere/$2|heyle lyste]] is ouk beskikbår.",
        "nolinkstoimage": "Geen enkelde syde gebrüükt disse holder.",
        "morelinkstoimage": "[[Special:WhatLinksHere/$1|Meer verwiezingen]] naor dit bestaand bekieken.",
        "linkstoimage-redirect": "$1 (bestaandsdeurverwiezing) $2",
        "unwatchthispage": "Niet volgen",
        "notanarticle": "Gien artikel",
        "notvisiblerev": "Bewarking is vortedaon",
-       "watchlist-details": "Der {{PLURAL:$1|steet één zied|staon $1 ziejen}} op joew volglieste, zonder de overlegziejen mee-erekend.",
+       "watchlist-details": "Der {{PLURAL:$1|steyt eyn syde|stån $1 syden}} up juw volglyste (plus oaverlegsyden).",
        "wlheader-enotif": "Je kriegen bericht per netpost",
        "wlheader-showupdated": "Ziejen die sinds joew leste bezeuk bie-ewörken bin staon '''vet'''.",
-       "wlnote": "Hieronder {{PLURAL:$1|steet de leste wieziging|staon de leste $1 wiezigingen}} in {{PLURAL:$2|t aofgeleupen ure|de leste $2 uren}} vanaof $3 um $4.",
+       "wlnote": "Hyrunder {{PLURAL:$1|steyt de lätste wysiging|stån de lätste <strong>$1</strong> wysigingen}} in {{PLURAL:$2|et vöärbye ure|de vöärbye $2 uren}} vanaf $3 üm $4.",
        "watchlist-submit": "Bekiek",
        "wlshowhideminor": "kleine bewarkingen",
        "watchlist-options": "Opsies veur de volglieste",
        "namespace_association": "Bybehöyrende naamruumde",
        "tooltip-namespace_association": "Vink dit vakjen an um ouk de oaverleg- en underwarpnaamruumde in te slüten dee by de selekteerde naamruumde höyret.",
        "blanknamespace": "(Höyvdnaamruumde)",
-       "contributions": "{{GENDER:$1|Gebrukersbydragen}}",
+       "contributions": "{{GENDER:$1|Brukersbydragen}}",
        "contributions-title": "Biedragen van $1",
        "mycontris": "Myn bydragen",
        "anoncontribs": "Bydragen",
        "tooltip-pt-preferences": "{{GENDER:|Miene}} vuurkeuren",
        "tooltip-pt-watchlist": "Lieste van zieden die op miene volglieste stoan",
        "tooltip-pt-mycontris": "Oaverzicht van {{GENDER:|oew}} biejdreagen",
-       "tooltip-pt-login": "Jy wördt van harte uutnöygd üm ju an te melden as bruker, mar et is neet verplicht",
+       "tooltip-pt-login": "Jy wördet van harte uutnöygd üm ju an te melden as bruker, mär et is neet verplicht",
        "tooltip-pt-logout": "Ofmaelden",
        "tooltip-pt-createaccount": "Skryv juw eigen vöäral in en meld juw eigen an. Dit is lykewels neet verplicht.",
        "tooltip-ca-talk": "Låt een oaverlegtekst oaver disse syde seen",
        "yesterday-at": "Gisteren um $1",
        "bad_image_list": "De opmaak is as volgt:\n\nAllinnig regels in n lieste (regels die beginnen mit *) wörden verwarkt.\nDe eerste verwiezing op n regel mut n verwiezing ween naor n ongewunst bestaand.\nAlle volgende verwiezingen die op de zelfde regel staon, wörden behaandeld as uutzundering, zo as ziejen waorop t bestaand in te tekste op-eneumen is.",
        "metadata": "Metadata",
-       "metadata-help": "In dit bestaand zit metadata mit EXIF-informasie, die deur n fotokamera, inleesapparaot of fotobewarkingsprogramma op-estuurd kan ween.",
+       "metadata-help": "Dit bestand bevat ekstra informaty, dee wårskynlik döär een fotokamera, skänner of fotobewarkingsprogramma upladen binnet.\nAs dit betand bewarked is, kommen möägelik bepålde details neet oavereyne mid et ewysigde bestand.",
        "metadata-expand": "Bekiek uutebreiden gegevens",
        "metadata-collapse": "Verbarg uutebreiden gegevens",
        "metadata-fields": "De afbealdingsmetadatavelden in dit bericht ståt ouk up een afbealdingssyde as de metadatatabel inklapped is.\nAndere velden wördet verbörgen.\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",
        "confirm-purge-title": "Herny disse syde",
        "confirm_purge_button": "Bevestig",
        "confirm-purge-top": "Klik up 'bevestig' üm et tüskengehöägen van disse syde te leagen.",
-       "confirm-purge-bottom": "Et leagmaken van et tüskengehöägen sörgt dervöär dat jy de lätste versy van een syde te seen krygen.",
+       "confirm-purge-bottom": "Et leadigmaken van et tüskengehöägen sörgt dervöär dat jy de lätste versy van een syde te seen kryget.",
        "confirm-watch-button": "Oké",
        "confirm-watch-top": "Disse zied op joew volglieste zetten?",
        "confirm-unwatch-button": "Oké",
        "version-entrypoints-header-entrypoint": "Ingang",
        "version-entrypoints-header-url": "Webadres",
        "redirect": "Deurverwiezen op bestaandsnaam, gebrukers-, zied-, versie- of logboekregelnummer",
-       "redirect-summary": "Disse spesiale zied verwis deur naor n bestaand (as de bestaandsnaam op-egeven wördt), n zied (as n zied- of versienummer op-egeven wördt) of n gebrukerszied (as t gebrukersnummer op-egeven wördt). Gebruuk: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], of [[{{#Special:Redirect}}/user/101]].",
+       "redirect-summary": "Disse speciale syde verwist döär nå een bestand (as de bestandsname upgeaven wördt), een syde (as een syd- of versynummer upgeaven wördt), een brukerssyde (as et brukersnummer upgeaven wördt) of een logbookinskryving (as een logbooknummer upgeaven wördt). Gebruuk: [[{{#Special:Redirect}}/file/Vöärbeald.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] of [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Zeuk",
        "redirect-lookup": "Opzeuken:",
        "redirect-value": "Weerde:",
index eb7b6d5..2ea7cdc 100644 (file)
        "undo-norev": "De bewerking kon niet ongedaan gemaakt worden, omdat die niet bestaat of is verwijderd.",
        "undo-nochange": "De bewerking lijkt al ongedaan gemaakt te zijn.",
        "undo-summary": "Versie $1 van [[Special:Contributions/$2|$2]] ([[User talk:$2|overleg]]) ongedaan gemaakt",
+       "undo-summary-anon": "Versie $1 van [[Special:Contributions/$2|$2]] ongedaan gemaakt",
        "undo-summary-username-hidden": "Versie $1 door een verborgen gebruiker ongedaan gemaakt",
        "cantcreateaccount-text": "Registreren vanaf dit IP-adres ('''$1''') is geblokkeerd door [[User:$3|$3]].\n\nDe door $3 opgegeven reden is ''$2''",
        "cantcreateaccount-range-text": "Het aanmaken van gebruikers vanaf IP-adressen in de range <strong>$1</strong> is niet mogelijk doordat dit is ingesteld door [[User:$3|$3]]. Uw IP-adres $4 bevindt zich in deze range.\n\nDe reden voor de blokkade is <em>$2</em>",
        "alreadyrolled": "Het is niet mogelijk om de bewerking van de pagina [[:$1]] door [[User:$2|$2]] ([[User talk:$2|overleg]]{{int:pipe-separator}}[[Special:Contributions/$2|bijdragen]]) ongedaan te maken.\nIemand anders heeft deze pagina al bewerkt of hersteld naar een eerdere versie.\n\nDe meest recente bewerking is gemaakt door [[User:$3|$3]] ([[User talk:$3|overleg]]{{int:pipe-separator}}[[Special:Contributions/$3|bijdragen]]).",
        "editcomment": "De bewerkingssamenvatting was: <em>$1</em>.",
        "revertpage": "Wijzigingen door [[Special:Contributions/$2|$2]] ([[User talk:$2|Overleg]]) hersteld tot de laatste versie door [[User:$1|$1]]",
+       "revertpage-anon": "Wijzigingen door [[Special:Contributions/$2|$2]] teruggedraaid naar de laatste versie door [[User:$1|$1]]",
        "revertpage-nouser": "Wijzigingen door een verborgen gebruiker teruggedraaid naar de laatste versie door {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Wijzigingen door {{GENDER:$3|$1}} ongedaan gemaakt;\nlaatste versie van {{GENDER:$4|$2}} hersteld.",
        "sessionfailure-title": "Sessiefout",
index 8b9debd..e55b753 100644 (file)
        "tog-usenewrc": "ߞߙߎ ߡߊߝߊ߬ߟߋ߲߬ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߐ߯ߟߕߊ ߟߎ߬ ߣߌ߫ ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫.",
        "tog-numberheadings": "ߕߍ߰ߟߌ ߡߐ߬ߟߐ߲ ߠߎ߬ ߝߙߍߕߍ߫ ߞߍ߲ߖߘߍߡߊߓߟߏߡߊ߬",
        "tog-editondblclick": "ߞߏߜߍ ߟߎ߬ ߡߊߦߟߍ߬ߡߊ߲߫ ߛߐ߲߬ߞߌ߲߬ߠߌ߲߬ߞߏ ߝߌ߬ߟߊ߬ ߟߊ߫",
+       "tog-editsectiononrightclick": "ߕߍߕߎ߲߮ ߞߌߣߌ߲ߝߍ߫ ߡߊߦߟߍߡߊ߲ߠߌ߲ ߕߍ߫ ߕߍߕߎ߲߮ ߞߎ߲߬ߕߐ߮ ߟߊ߫.",
        "tog-watchcreations": "ߒ ߠߊ߫ ߞߐߜߍ߫ ߛߌ߲ߘߌ߫ ߣߍ߲ ߠߎ߬ ߣߌ߫ ߞߐߕߐ߯ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬ ߓߌ߬ߟߊ߬ ߒ ߠߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫.",
        "tog-watchdefault": "ߒ ߠߊ߫ ߞߐߜߍ߫ ߣߌ߫ ߞߐߕߐ߯ ߡߊߦߟߍ߬ߡߊ߲߬ߣߍ߲ ߠߎ߬ ߓߌ߬ߟߊ߬ ߒ ߠߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫.",
        "tog-watchmoves": "ߒ ߠߊ߫ ߞߐߜߍ ߣߌ߫ ߞߐߕߐ߯ ߟߊߕߐߣߍ߲ ߠߎ߬ ߓߌ߬ߟߊ߬ ߒ ߠߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫.",
        "tog-watchdeletion": "ߒ ߠߊ߫ ߞߐߜߍ ߣߌ߫ ߞߐߕߐ߯ ߖߏ߬ߛߌ߬ߣߍ߲ ߠߎ߬ ߓߌ߬ߟߊ߬ ߒ ߠߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫.",
        "tog-watchuploads": "ߒ ߠߊ߫ ߞߐߕߐ߯ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬ ߓߌ߬ߟߊ߬ ߒ ߠߊ߫ ߜߋ߬ߟߎ߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫.",
+       "tog-watchrollback": "ߞߐߜߍ ߟߎ߬ ߟߊߘߏ߲߬ ߒ ߠߊ߫ ߦߟߌߡߊߛߙߋ ߟߛߊ߬ߦߌ߲߬ߣߍ߲߬ ߣߐ߬ߡߊߢߌ߲߬ߧߊ߬ߣߍ߲ ߠߎ߬ ߘߐ߫.",
        "tog-minordefault": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߘߋ߬ߣߍ߲ ߠߎ߬ ߣߐ߬ߣߐ߬ ߓߐߛߎ߲ ߘߌ߫",
        "tog-previewontop": "ߢߍߦߋߟߌ ߦߌ߬ߘߊ߬ ߡߊ߰ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߏ߲ߘߏ ߢߍ߫.",
        "tog-previewonfirst": "ߢߍߦߋߟߌ ߦߌ߬ߘߊ߬ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߝߟߐ ߘߐ߫.",
@@ -57,6 +59,8 @@
        "underline-default": "ߝߊ߬ߘߌ ߥߟߊ߫ ߛߏ߲߯ߓߊߟߊ߲ ߠߊ߫ ߝߍ߭",
        "editfont-style": "ߕߌ߲߬ߞߎߘߎ߲ ߛߓߍߛߎ߲ ߛߎ߯ߦߊ ߢߊߓߐ߫:",
        "editfont-monospace": "ߞߣߍߙߋ߲ ߛߓߍߛߎ߲",
+       "editfont-sansserif": "ߛߊ߲-ߛߋ߬ߙߌߝ ߞߟߏߜߍ",
+       "editfont-serif": "ߛߋ߬ߙߌߝ ߞߟߏߜߍ",
        "sunday": "ߞߊ߯ߙߌߟߏ߲",
        "monday": "ߞߐ߬ߓߊ߬ߟߏ߲",
        "tuesday": "ߞߐ߬ߟߏ߲",
        "privacy": "ߘߎ߲߬ߘߎ߬ߡߊ߬ ߓߘߍߓߘߍߟߌ",
        "privacypage": "Project:ߘߎ߲߬ߘߎ߬ߡߊ߬ ߓߘߍ߬ߓߘߍ߬ߟߌ",
        "badaccess": "ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ",
+       "badaccess-group0": "ߌ ߣߊ߬ ߞߏ ߡߍ߲ ߡߊߢߌ߬ߣߌ߲߬ߞߊ߬ ߟߴߏ߬ ߘߌ߫߸ ߌ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߍ߫ ߞߵߏ߬ ߞߍ߫.",
        "badaccess-groups": "ߌ ߣߊ߬ ߞߍߢߊ ߡߍ߲ ߡߊߢߌ߬ߣߌ߲߬ߞߊ߬ ߟߊ߫ ߣߌ߲߬߸ ߏ߬ ߟߊߓߊ߯ߙߊ ߡߊߓߍ߲߬ߣߍ߲߫ ߠߋ߫ ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߡߍ߲ ߠߎ߬ ߦߋ߫ {{PLURAL:$2|ߞߙߎ ߘߐ߫|ߞߙߎ ߘߏ߫ ߘߐ߫}}: $1.",
        "versionrequired": "ߡߊߘߌߦߊ߫-ߥߞߌ߫ ߓߊߦߟߍߡߊ߲ߠߌ߲ $1 ߞߊ߬ߣߌ߲߬ ߣߍ߲߫",
        "versionrequiredtext": "ߡߊߘߌߦߊ߫-ߥߞߌ߫ ߘߟߊߡߌߘߊߟߌ $1 ߞߊ߬ߣߌ߲߬ ߣߍ߲߫ ߞߊ߬ ߞߐߜߍ ߣߌ߲߬ ߠߊߓߊ߯ߙߊ߫.\n[[Special:Version|version page]] ߦߋ߫.",
        "nstab-category": "ߦߌߟߡߊ",
        "mainpage-nstab": "ߓߏ߬ߟߏ߲߬ߘߊ",
        "nosuchaction": "ߞߍߟߌ߫ ߛߎ߮ ߏ߬ ߕߴߦߋ߲߬",
+       "nosuchactiontext": "ߞߏ ߡߍ߲ ߞߙߍߞߙߍߣߍ߲ ߦߋ߫ URL ߓߟߏ߫߸ ߏ߬ ߓߍ߲߬ ߣߍ߲߬ ߕߍ߫.\nߌ ߝߟߌ߬ߣߍ߲߫ ߘߌ߫ ߞߍ߫ URL ߛߓߍ߫ ߕߎߡߊ ߟߊ߫߸ ߥߟߊ߫ ߛߘߌ߬ߜߋ߲ ߓߍ߲߬ߓߊߟߌ ߟߋ߬ ߟߊߓߊ߬ߕߏ߬ߣߍ߲߬ ߦߵߌ ߓߟߏ߫.\nߕߎ߬ߡߊ߬ߘߐ߫ ߏ߬ ߝߣߊ߫ ߘߴߊ߬ ߦߌ߬ߘߊ߬ ߞߏ߫ ߝߏ߬ߘߏ ߦߋ߫ ߛߎ߲ߝߘߍ߫ ߟߊߓߊ߯ߙߊߣߍ߲ ߘߐ߫  {{SITENAME}} ߓߟߏ߫.",
        "nosuchspecialpage": "ߘߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲߬ ߛߎ߮ ߏ߬ ߝߋ߲߫ ߕߍ߫ ߦߊ߲߬",
        "nospecialpagetext": "<strong>ߊߟߎ߫ ߓߘߊ߫ ߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲ ߘߏ߫ ߢߌߣߌ߲߫ ߡߍ߲ ߕߺߴߦߋ߲߬.</strong>\nߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲߫ ߓߘߍ߬ߡߊ ߟߎ߬ ߛߙߍߘߍ ߦߋ߫ ߢߌ߲߬ ߠߋ߫ ߞߊ߲߬ [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "ߝߎ߬ߕߎ߲߬ߕߌ",
        "databaseerror": "ߓߟߏߡߟߊ ߟߎ߬ ߝߊ߲ ߝߎ߬ߕߎ߲߬ߕߌ",
+       "databaseerror-text": "ߓߟߏߡߟߊߝߊ߲ ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ ߘߏ߫ ߓߘߴߊ߬ ߓߌ߬ߟߵߊ߬ ߘߐ߫.\nߏ߬ ߦߴߊ߬ ߦߌ߬ߘߊ߬ ߟߊ߫ ߟߋ߬ ߞߏ߫ ߝߏ߬ߘߏ ߦߋ߫ ߛߎ߲ߝߘߍ ߘߐ߫.",
        "databaseerror-textcl": "ߓߟߏߡߟߊߝߊ߲ ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ ߘߏ߫ ߓߘߊ߫ ߓߌ߬ߟߵߊ߬ ߘߐ߫.",
        "databaseerror-query": "ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ $1",
        "databaseerror-function": "ߗߋߘߊ $1",
        "databaseerror-error": "ߝߎ߬ߕߎ߲߬ߕߌ: $1",
+       "laggedslavemode": "<strong>ߖߊ߲߬ߓߌ߬ߟߊ߬ߟߌ</strong> ߟߏ߲ߘߐߦߊߟߌ߫ ߞߎߘߊ߫ ߛߌ߫ ߕߍ߫ ߞߐߜߍ ߘߐ߫.",
        "readonly": "ߓߟߏߡߟߊ ߝߊ߲ ߛߐ߰ߣߍ߲߫",
+       "enterlockreason": "ߛߐ߰ߟߌ ߞߎ߲߭ ߠߊߘߏ߲߬߸ ߞߵߏ߬ ߟߊ߫ ߛߐ߰ߟߌ ߓߊ߲߫ ߕߎߡߊ߫ ߖߊ߬ߕߋߡߌ߬ߘߊ߬ߣߍ߲ ߞߊ߲߬.",
        "missingarticle-rev": "(ߡߛߊ߬ߦߌ߲߬ߠߌ߲#:$1)",
        "missingarticle-diff": "(Diff: $1, $2)",
        "readonly_lag": "ߓߟߏߡߟߊ ߝߊ߲ ߓߘߊ߫ ߛߐ߰ ߞߍߒߖߘߍߦߋ߫ ߓߟߏߡߊ߬߸ ߞߊ߬ߦߌ߯ ߖߐ߲߬ ߓߟߏߡߟߊ ߝߊ߲ ߡߊߛߐߓߊ߮ ߡߌ߬ߣߊ߬ ߘߊ߫ ߞߙߊ߬ߡߐ߮ ߓߟߏ߫.",
        "invalidtitle-unknownnamespace": "ߞߎ߲߬ߕߐ߮ ߓߍ߲߬ߓߊߟߌ ߞߊ߬ ߓߍ߲߬ ߕߐ߯ߛߓߍ ߞߣߍ߫ ߡߊߟߐ߲ߓߊߟߌ ߝߙߍߕߍ ߡߊ߬ $1 ߊ߬ ߣߌ߫ ߛߓߍߟߌ  \"$2\"",
        "exception-nologin": "ߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲߬ ߕߍ߫",
        "exception-nologin-text": "ߌ ߜߊ߲߬ߞߎ߲߫ ߖߊ߰ߣߌ߲߬߸ ߛߴߌ ߘߌ߫ ߛߋ߫ ߞߐߜߍ ߣߌ߲߬ ߡߊߛߐ߬ߘߐ߲߬ ߠߊ߫ ߥߟߊ߫ ߝߏ߲߬ߝߏ߲.",
+       "virus-scanfailed": "ߕߎ߬ߡߊ߬ߢߐ߲߰ߦߊ ߓߘߊ߫ ߗߌߙߏ߲߫ (ߘߏߝߙߍߕߍ $1)",
        "virus-unknownscanner": "ߢߐߛߌߙߋ߲ߞߟߊ߬ ߡߊߟߐ߲ߓߊߟߌ",
        "logouttext": "<strong>ߌ ߜߊ߲߬ߞߎ߲߬ߓߐ߬ߣߍ߲߬ ߕߍ߫.</strong>\n\nߞߐߜߍ ߘߏ߫ ߟߎ߫ ߕߘߍ߬ ߘߌ߫ ߞߍ߫ ߓߊ߯ߙߊ߫ ߟߊ߫ ߞߵߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲ ߕߏ߫߸ ߝߏ߫ ߣߴߌ ߞߵߌ ߟߊ߫ ߛߏ߲߯ߓߊߟߊ߲ ߢߡߊߘߏ߲߰ߣߍ߲ ߠߎ߬ ߖߏ߬ߛߌ߬.",
        "logging-out-notify": "ߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲ ߓߐ ߦߴߌ ߘߐ߫߸ ߡߊ߬ߞߐ߬ߣߐ߲߬ߠߌ߲ ߞߍ߫ ߖߊ߰ߣߌ߲߬.",
        "noemailcreate": "ߌ ߞߊߞߊ߲߫ ߞߊ߬ ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ߫ ߞߟߊ߬ߟߊ߬ߡߊ ߘߏ߫ ߡߊߛߐ߫.",
        "passwordsent": "ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߡߍ߲ ߦߋ߫ \"$1\" ߟߊ߫߸ ߕߊ߬ߡߌ߲߬ߞߊ߲߬ ߞߎߘߊ߫ ߓߘߊ߫ ߗߋ߫ ߏ߬ ߡߊ߬. ߖߊ߰ߣߌ߲߬ ߣߴߌ ߞߵߊ߬ ߡߊߛߐ߬ߘߐ߲߬ ߌ ߦߴߌ ߜߊ߲߬ߞߎ߲߬ ߕߎ߲߯.",
        "blocked-mailpassword": "ߌ ߟߊ߫ IP ߓߘߊ߫ ߓߊ߬ߟߊ߲߬ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߏ ߘߐ߫. ߖߐ߲߬ߛߊ߫ ߞߊ߬ ߘߊ߲߬ߠߊ߬ߕߊߡߌ߲ ߢߍߓߍ߲߬߸ \nߕߊ߬ߡߌ߲߬ߞߊ߲߬ ߡߊ߬ߛߐߘߐ߲ ߠߊߘߤߊ߬ߣߍ߲߬ ߕߍ߫ IP ߛߊ߲߬ߓߊ߬ߕߐ߮ ߣߌ߲߬ ߠߊ߫.",
+       "eauthentsent": "ߟߊ߬ߛߙߋ߬ߦߊ߬ߟߌ߬ ߗߋߛߓߍ ߓߘߊ߫ ߗߋ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ߫ ߛߊ߲߬ߓߊ߬ߕߐ߰ ߞߙߍߞߙߍߣߍ߲ ߡߊ߬.\nߦߊ߲߬ߣߌ߫ ߗߋߛߓߍ߫ ߜߘߍ ߛߎ߯_ߎ߯_ߛߎ߫ ߗߋ߫ ߕߍ߫ ߖߊ߬ߕߋ߬ߘߊ ߡߊ߬߸ ߌ ߞߊߞߊ߲߫ ߞߊ߬  ߢߍߡߌߘߊߟߌ ߟߊߓߊ߬ߕߏ߬ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߘߐ߫߸ ߞߵߊ߬ ߟߊߛߙߋߦߊ߫ ߞߏ߫ ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߦߴߌ ߕߊ ߟߋ߬ ߘߌ߫.",
+       "throttled-mailpassword": "ߕߊ߬ߡߌ߲߬ߞߊ߲ ߡߊߝߊ߬ߟߋ߲߬ ߗߋߛߓߍ ߓߘߊ߫ ߓߊ߲߫ ߗߋ߫ ߟߊ߫ {{PLURAL:$1|ߕߎ߬ߡߊ߬ߙߋ߲|ߕߎ߬ߡߊ߬ߙߋ߲ $1 ߠߎ߬}} ߕߊ߬ߡߌ߲߬ߣߍ߲ ߠߎ߬ ߞߘߐ߫.\nߞߵߊ߬ ߟߊߥߟߌ߬ ߘߊ߲߬ߠߊ߬ߕߊߡߌ߲ ߢߍߓߍ߲߭ ߡߊ߬߸ ߕߊ߬ߡߌ߲߬ߞߊ߲ ߡߊߝߊ߬ߟߋ߲߬ߠߌ߲߬ ߞߋߟߋ߲߫ ߠߋ߬ ߗߋ߫ ߟߊ߫ {{PLURAL:$1|ߕߎ߬ߡߊ߬ߙߋ߲|ߕߎ߬ߡߊ߬ߙߋ߲ $1 ߠߎ߬}} ߞߘߐ߫.",
        "mailerror": "ߢߎߡߍߙߋ߲ ߗߋߟߌ ߝߎ߬ߕߎ߲߬ߕߌ:$1",
        "acct_creation_throttle_hit": "ߥߞߌ ߣߌ߲߬ ߓߐߒߡߟߊ ߟߎ߬ ߦߴߌ ߟߊ߫ IP ߛߊ߲߬ߓߊ߬ߕߐ߮ ߟߊߓߊ߯ߙߊ߫ ߟߊ߫߸ ߊ߬ߟߎ߬ ߓߘߊ߫ ߖߊ߬ߕߋ߬ߘߊ߬ {{PLURAL:$1|ߖߊ߬ߕߋ߬ߘߊ߬ ߁|$1ߖߊ߬ߕߋ߬ߘߊ ߟߎ߬}} ߛߌ߲ߘߌ߫߸ ߞߐ߯ߟߕߊ $2,ߟߋ߬ ߦߴߊ߬ ߞߐߘߊ߲߫ ߠߊߘߤߊ߬ߣߍ߲ ߘߌ߫ ߥߊ߯ߕߌ ߣߌ߲߬ ߠߊ. ߞߐߖߋߓߌ ߘߐ߫߸ ߓߐߒߡߟߊ ߟߎ߬ ߦߋ߫ IP ߛߊ߲߬ߓߊ߬ߕߐ߮ ߣߌ߲߬ ߠߋ ߟߊߓߊ߯ߙߊ߫ ߟߊ߫ ߕߊ߲߬߸ ߏ߬ ߘߏ߲߬ ߕߴߛߋ߫ ߖߊ߬ߕߋ߬ߘߊ߬ ߜߘߍ߫ ߛߌ߲ߘߌ߫ ߟߊ߫ ߥߊ߯ߕߌ ߣߌ߲߬ ߠߴߏ߬ ߞߐ߫.",
        "emailauthenticated": "ߌ ߟߊ߫ ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߟߊߛߙߋߦߊ߫ ߘߊ߫ $3 $2 ߟߊ߫",
        "emailnotauthenticated": "ߌ ߟߊ߫ ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߡߊ߫ ߟߊߛߙߋߦߊ߫ ߡߎߣߎ߲߬.\nߢߎߡߍߙߋ߲߫ ߕߍ߫ ߛߋ߫ ߗߋ߫ ߟߴߌ ߡߊ߬ ߘߊߞߎ߲ ߢߌ߲߬ ߠߎ߫ ߞߊ߲߬.",
+       "noemailprefs": "ߢߎߡߍߙߋ߲ߞߏ߲ߘߏ ߘߏ߫ ߡߊߕߍ߰ ߌ ߟߊ߫ ߦߟߌߡߊߛߙߋ ߘߐ߫ ߛߊ߫ ߓߊ߯ߙߢߊ ߣߌ߲߬ ߘߌ߫ ߓߊ߯ߙߊ߫.",
        "emailconfirmlink": "ߌ ߟߊ߫ ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߟߊߛߙߋߦߊ߫.",
        "invalidemailaddress": "ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߣߌ߲߬ ߕߍߣߊ߬ ߛߋ߫ ߟߊ߫ ߟߊߡߌ߬ߣߊ߬ ߟߊ߫߸ ߓߴߊ߬ ߦߋ߫ ߣߍ߲߫ ߦߋ߫ ߖߙߎߡߎ߲߫ ߓߍ߲߬ߓߊߟߌ ߟߋ߬ ߘߌ߫.\nߖߊ߰ߣߌ߲߬ ߌ ߦߋ߫ ߛߊ߲߬ߓߊ߬ߕߐ߮ ߖߙߎߡߎ߲߫ ߓߍ߲߬ߣߍ߲ ߘߏ߫ ߟߊߘߏ߲߬߸ ߥߟߊ߫ ߘߐ߬ߞߏߟߏ߲ ߡߍ߲ ߗߌߙߏ߲߫ ߣߍ߲߫.",
        "cannotchangeemail": "ߖߊ߬ߕߋ߬ߘߊ ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߕߴߛߋ߫ ߡߊߝߊ߬ߟߋ߲߬ ߠߊ߫ ߥߞߌ ߣߌ߲߬ ߘߐ߫.",
        "emaildisabled": "ߞߍߦߙߐ ߣߌ߲߬ ߕߍߣߊ߬ ߛߋ߫ ߟߊ߫ ߢߎߡߍߙߋ߲߫ ߗߋ߫ ߟߊ߫.",
        "accountcreated": "ߖߊ߬ߕߋ߬ߘߊ ߓߘߊ߫ ߛߌ߲ߘߌ߫",
+       "accountcreatedtext": "[[{{ns:User}}:$1|$1]] ߟߊ߫ ߖߊ߬ߕߋ߬ߘߊ߬ ߟߊߓߊ߯ߙߕߊ ([[{{ns:User talk}}:$1|talk]]) ߓߘߊ߫ ߓߊ߲߫ ߛߌ߲ߘߌ߫ ߟߊ߫.",
        "createaccount-title": "ߖߊ߬ߕߋ߬ߘߊ ߓߘߊ߫ ߟߊߞߊ߬  {{SITENAME}} ߢߍ߫.",
        "createaccount-text": "ߡߐ߱ ߘߏ߫ ߓߘߊ߫ ߖߊ߬ߕߋ߬ߘߊ ߘߏ߫ ߛߌ߲ߘߴߌ ߟߊ߫ ߢߎߡߍߙߋ߲߫ ߞߏ߲ߘߏ ߣߌ߲߬ ߡߊ߬ ($4) ߡߍ߲ ߕߐ߯ߟߊ߫ ߣߍ߲߫ ߞߏ߫ \"$2\"߸ ߕߊ߬ߡߌ߲߬ߞߊ߲ ߣߌ߲߬ ߠߊ߫  \"$3\". ߌ ߦߴߌ ߜߊ߲߬ߞߎ߲߬ ߞߵߌ ߟߊ߫ ߕߊ߬ߡߌ߲߬ߞߊ߲ ߡߊߝߊ߬ߟߋ߲߬ ߌߞߘߐ߫߹\n\nߌ ߦߋ߫ ߗߋߛߓߍߊ ߡߊߓߌ߬ߟߊ߬߸ ߣߌ߫ ߖߊ߬ߕߋ߬ߘߊ ߟߊߞߊ߬ߣߍ߲߬ ߞߍ߫ ߘߊ߫ ߝߎ߬ߕߎ߲߬ߕߌ߬ ߓߟߏߡߊ߬",
+       "login-throttled": "ߌ ߓߘߴߌ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߡߊߛߊ߬ߦߌ߫ ߛߋ߲߬ߧߊ߬ ߛߌߦߊߡߊ߲߫ ߞߏߖߎ߰.\nߌ ߘߐߟߐ߬ $1 ߞߘߐ߫ ߦߊ߬ߣߴߌ ߦߴߊ߬ ߡߊߝߍߣߍ߲߫ ߕߎ߲߯.",
        "login-abort-generic": "ߌ ߟߊ߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߓߘߊ߫ ߗߌߙߏ߲߫ - ߕߌߢߍ߫",
        "login-migrated-generic": "ߌ ߟߊ߫ ߖߊ߬ߕߋ߬ߘߊ ߓߘߊ߫ ߝߎ߲ߘߌ߫߸ ߊ߬ ߣߴߌ ߟߊ߫ ߟߊ߬ߓߊ߰ߙߊ߬ ߕߐ߮ ߕߍ߫ ߣߊ߬ ߥߊ߯ߕߌߖߊ߲߫ ߞߍ߫ ߟߊ߫ ߥߞߌ ߣߌ߲߬ ߠߊ߫ ߦߊ߲߬ ߏ߬ ߞߐ߫.",
        "loginlanguagelabel": "ߞߊ߲ $1",
+       "suspicious-userlogout": "ߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲ ߓߐ߫ ߞߏ ߡߊ߬ߢߌ߬ߣߌ߲߬ߠߌ߲ ߟߊߕߐ߲ߣߍ߲߫ ߠߋ߬ ߓߊߏ߬ ߊ߬ ߞߍߣߍ߲߫ ߦߏ߫ ߊ߬ ߗߋ߫ ߣߍ߲߫ ߦߋ߫ ߛߏ߲߯ߓߊߟߊ߲߫ ߖߐ߲ߝߐ߲ߣߍ߲ ߠߋ߬ ߓߟߏ߫ ߥߟߊ߫ ߟߐ߲߬ߞߋ߬ߟߊ ߡߊߛߐߟߊ߫ (ߔߑߙߏ߬ߞߛߌ) ߢߡߊߘߏ߲߰ߣߍ߲.",
        "pt-login": "ߌ ߜߊ߲߬ߞߎ߲߬",
        "pt-login-button": "ߌ ߜߊ߲߬ߞߎ߲߬",
        "pt-login-continue-button": "ߌ ߟߊ߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߘߊߓߊ߲߫",
        "editpage-cannot-use-custom-model": "ߞߐߜߍ ߣߌ߲߬ ߞߣߐߘߐ ߛߎ߮ߦߊ ߕߍߣߊ߬ ߛߐ߲߬ ߠߊ߫ ߡߊߦߟߍ߬ߡߊ߲߫ ߠߊ߫.",
        "templatesused": "{{PLURAL:$1|ߞߙߊߞߏ|ߞߙߊߞߏ ߟߎ߫}} ߟߎ߫ ߟߊߓߊ߯ߙߊ߫ ߘߊ߫ ߞߐߜߍ ߣߌ߲߬ ߘߐ߫",
        "templatesusedpreview": "{{PLURAL:$1|ߞߙߊߞߏ|ߞߙߊߞߏ ߟߎ߬}} ߟߋ߬ ߟߊߓߊ߯ߙߊ߫ ߣߍ߲߫ ߢߍߦߋߟߌ ߣߌ߲߬ ߘߐ߫",
+       "templatesusedsection": "{{PLURAL:$1|ߞߙߊߞߏ|ߞߙߊߞߏ ߟߎ߬}} ߟߋ߬ ߟߊߓߊ߯ߙߊ߫ ߣߍ߲߫ ߕߍߕߍ߮ ߣߌ߲߬ ߘߐ߫:",
        "template-protected": "(ߊ߬ ߡߊߞߊ߲ߞߊ߲ߣߍ߲߫ ߠߋ߬)",
        "template-semiprotected": "(ߟߊ߬ߞߊ߲߬ߘߊ߬ߟߌ-ߝߊ߲߬ߞߋ߬ߟߋ߲߬ߡߊ)",
        "hiddencategories": "ߞߐߜߍ ߣߌ߲߬ ߦߋ߫ ߢߌ߲߬ ߠߎ߫ ߛߌ߲߬ߝߏ߲ ߠߋ߬ ߘߌ߫{{PLURAL:$1|}}",
+       "nocreatetext": "{{SITENAME}} ߓߘߴߊ߬ ߛߋߞߏߦߊ ߟߊߞߎ߲߬ߛߌ߲߫ ߞߊ߬ ߞߐߜߍ߫ ߞߎߘߊ߫ ߟߊߘߊ߲߫.\nߌ ߘߌ߫ ߛߴߌ ߞߐߛߊ߬ߦߌ߬ ߟߊ߫ ߞߊ߬ ߛߋ߲߬ߠߊ߬ ߞߐߜߍ ߡߊߦߟߍ߬ߡߊ߲߫ ߸ ߥߟߊ߫  [[Special:UserLogin|log in or create an account]].",
        "nocreate-loggedin": "ߞߐߜߍ߫ ߞߎߘߊ߫ ߛߌ߲ߘߌ߫ ߞߏ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߴߌ ߦߋ߫.",
+       "sectioneditnotsupported-title": "ߘߌ߬ߢߍ߬ ߞߍߣߍ߲߫ ߕߍ߫ ߕߍߕߍ߮ ߡߊߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߊ߬.",
        "sectioneditnotsupported-text": "ߛߌ߰ߘߊ ߡߊߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߠߊߘߤߊ߬ߣߍ߲߬ ߕߍ߫ ߞߐߜߍ ߣߌ߲߬ ߠߊ߫ ߕߊ߲߬.",
+       "modeleditnotsupported-title": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߍ߫",
+       "modeleditnotsupported-text": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߍ߫ ߞߣߐߘߐ ߛߎ߮ߦߊ $1 ߦߋ߫.",
        "permissionserrors": "ߝߌ߬ߟߌ߫ ߘߌ߬ߢߍ߬ߒߧߋ",
        "permissionserrorstext": "ߌ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߍ߫ ߞߵߏ߬ ߞߍ߫߸ ߣߌ߲߬ ߠߊ߫ {{PLURAL:$1|ߛߊߓߎ|ߛߊߓߎ ߟߎ߬}}:",
        "permissionserrorstext-withaction": "ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ߬ ߛߌ߫ ߕߴߌ ߦߋ߫ ߞߊ߬ $2߸ {{PLURAL:$1|ߞߏߛߐ߲߬|ߟߎ߬ ߞߏߛߐ߲߬}}",
        "contentmodelediterror": "ߌ ߕߍߣߊ߬ ߛߋ߫ ߟߊ߫ ߛߌ߰ߘߊ ߣߌ߲߬ ߡߊߦߟߍ߬ߡߊ߲߬ ߠߊ߫߸ ߓߊߏ߬ ߞߣߐߘߐ ߛߎ߯ߦߊ ߦߋ߫ <code>$1</code> ߟߋ߬ ߘߌ߫߸ ߡߍ߲ ߦߋ߫ ߕߋ߲߬ߕߋ߲߬ ߞߣߐߘߐ ߛߎ߯ߦߊ ߝߘߏ߬ ߟߊ߫ ߞߐߜߍ <code>$2</code> ߘߐ߫.",
        "recreate-moveddeleted-warn": "<strong>ߌ ߖߊ߲߬ߕߏ߫: ߌ ߦߋ߫ ߞߐߜߍ ߘߏ߫ ߟߋ߬ ߟߊߘߊ߲߫ ߞߏ ߘߐ߫ ߣߌ߲߬߸ ߡߍ߲ ߖߏ߬ߛߌ߬ߣߍ߲߬ ߡߎߣߎ߲߬.</strong> \nߌ ߓߛߌ߬ߞߌ߬ ߕߐ߫ ߟߋ߬ ߛߍ߲߸ ߣߴߌ ߘߌ߫ ߛߋ߫ ߞߐߜߍ ߣߌ߲߬ ߡߊߦߟߍ߬ߡߊ߲ ߘߊߓߊ߲߫ ߠߊ߫. \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-created": "ߞߐߜߍ ߓߘߊ߫ ߓߊ߲߫ ߛߌ߲ߘߌ߫ ߟߊ߫.",
        "right-minoredit": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߣߐ߬ߣߐ߬ ߡߌ߬ߛߍ߬ߡߊ߲ ߘߌ߫",
        "right-move": "ߞߐߜߍ ߟߎ߬ ߛߋ߲߬ߓߐ߫",
        "right-move-subpages": "ߞߐߜߍ ߛߋ߲߬ߓߐ߫ ߊ߬ߟߎ߬ ߟߊ߫ ߞߐߜߍߙߋ߲ ߠߎ߬ ߘߐ߫",
+       "right-move-rootuserpages": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ߫ ߟߌ߯ߟߌ߲ߖߌ߰ߣߍ߲ ߞߐߜߍ ߛߋ߲߬ߓߐ߫",
        "right-move-categorypages": "ߦߌߟߡߊ߫ ߞߐߜߍ ߟߎ߬ ߛߋ߲߬ߓߐ߫",
        "right-movefile": "ߞߐߕߐ߮ ߟߎ߬ ߛߋ߲߬ߓߐ߫",
        "right-upload": "ߞߐߕߐ߮ ߟߎ߬ ߟߊߦߟߍ߬",
        "right-sendemail": "ߢߎߡߍߙߋ߲ ߗߋ߫ ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߘߏ ߟߎ߬ ߡߊ߬",
        "grant-generic": "\"$1\" ߞߌߣߌ߲߫ ߝߍ߫ ߝߎߝߎ",
        "grant-group-page-interaction": "ߞߐߜߍ ߟߎ߬ ߟߊ߫ ߞߏߢߐ߲߯ߦߊ",
+       "grant-group-watchlist-interaction": "ߌ ߟߊ߫ ߦߟߌߡߊߛߙߋ ߞߏߢߐ߲߯ߦߊ",
        "grant-group-email": "ߢߎߡߍߙߋ߲ ߗߋ߫",
+       "grant-group-customization": "ߘߎ߲߬ߘߎ߬ߡߦߊ߬ߟߌ ߣߌ߫ ߦߟߌߡߊߛߙߋ",
        "grant-blockusers": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߟߎ߬ ߓߊ߬ߟߌ ߣߴߊ߬ߟߎ߬ ߓߊ߬ߟߌ߬ߣߍ߲ ߓߐߟߌ",
        "grant-createaccount": "ߖߊ߬ߕߋ߬ߘߊ ߘߏ߫ ߛߌ߲ߘߌ߫",
        "grant-createeditmovepage": "ߞߐߜߍ ߛߌ߲ߘߌ߫߸ ߡߊߦߟߍ߬ߡߊ߲߫߸ ߊ߬ ߣߌ߫ ߞߵߊ߬ ߛߋ߲߬ߓߐ߫",
        "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": "ߞߐߕߐ߮ ߣߌ߲߬ ߠߊߦߟߍ߬ ߞߊ߬ ߓߐ߫ URL ߘߐ߫",
        "action-writeapi": "ߛߓߍߟߌ API ߟߊߓߊ߯ߙߊ߫",
        "action-delete": "ߞߐߜߍ ߣߌ߲߬ ߖߏ߰ߛߌ߬",
        "action-deleterevision": "ߟߢߊ߬ߟߌ ߟߎ߬ ߖߏ߬ߛߌ߬",
        "action-deletedhistory": "ߞߐߜߍ ߟߎ߬ ߖߏ߰ߛߌ߬ߟߌ ߘߐ߬ߝߐ ߦߋ߫",
+       "action-deletedtext": "ߟߢߊ߬ߟߌ ߖߏ߰ߛߌ߬ߣߍ߲ ߠߎ߬ ߛߓߍߟߌ ߦߋ߫.",
        "action-browsearchive": "ߞߐߜߍ߬ ߖߏ߰ߛߌ߬ߣߍ߲ ߠߎ߬ ߢߌߣߌ߲߫",
        "action-undelete": "ߞߐߜߍ ߖߏ߰ߛߌ߬ߓߊߟߌ ߟߎ߬",
        "action-suppressionlog": "ߘߎ߲߬ߘߎ߬ߡߊ߬ ߘߎ߲ߛߓߍ ߣߌ߲߬ ߦߋ߫",
        "upload-description": "ߞߐߕߐ߮ ߞߊ߲߬ߛߓߍߟߌ",
        "upload-options": "ߟߊ߬ߦߟߍ߬ߟߌ ߢߣߊߕߊߟߌ",
        "watchthisupload": "ߞߐߕߐ߮ ߣߌ߲߬ ߘߐߜߍ߫",
+       "upload-proto-error": "ߡߛߍ߬ߞߍ߬ߡߛߍߞߍ ߓߍ߲߬ߓߊߟߌ",
        "upload-file-error": "ߞߣߐߟߊߘߐ߫ ߝߎߕߎ߲ߕߌ",
        "upload-misc-error": "ߟߊ߬ߦߟߍ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ߬ ߡߊߟߐ߲ߓߊߟߌ",
        "upload-misc-error-text": "ߝߎ߬ߕߎ߲߬ߕߌ߬ ߡߊߟߐ߲ߓߊߟߌ ߘߏ߫ ߓߘߊ߫ ߓߌ߬ߟߵߊ߬ ߘߐ߫ ߟߊ߬ߦߟߍ߬ߟߌ ߞߎ߲߬ߕߊ߮ ߞߘߐ߫. ߊ߬ ߝߛߍ߬ߝߛߍ߬ߟߌ ߞߍ߫ ߞߵߊ߬ ߟߐ߲߫ ߣߌ߫ URL ߓߍ߲߬ߣߍ߲߬ ߊ߬ ߣߴߊ߬ ߡߊߛߐ߬ߘߐ߲߬ߕߊ ߦߋ߫ ߞߣߊ߬ ߕߴߊ߬ ߡߊߝߍߣߍ߲߫ ߣߴߏ߬ ߞߐ߫.\nߣߌ߫ ߝߙߋߞߋ ߘߏ߲߬ ߠߊߝߛߊ߬ ߘߊ߫߸ ߓߌ߬ߟߊ߬ߢߐ߲߰ߡߊ ߢߌߣߌ߲߫ [[Special:ListUsers/sysop|administrator]] ߝߍ߬.",
        "backend-fail-read": "ߞߐߕߐ߮ ߕߴߛߋ߫ ߘߐߞߊ߬ߙߊ߲߬ ߠߊ߫   \"$1\".",
        "backend-fail-create": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߐ߲߬ ߠߊ߫ ߞߐߕߐ߮  \"$1\" ߛߓߍ߫ ߟߊ߫.",
        "backend-fail-maxsize": "ߊ߫ ߕߍ߫ ߣߊ߬ ߞߐߕߐ߮  \"$1\" ߛߓߍ߫ ߟߊ߫߸ ߓߊߏ߬ ߊ߬ ߓߏ߲߬ߓߊ߫ ߞߊ߬ ߕߊ߬ߡߌ߲߬ {{PLURAL:$2|ߝߙߐ߬ߢߐ߬ ߞߋߟߋ߲߫|ߝߙߐ߬ߢߐ ߟߎ߬ $2}}.",
+       "backend-fail-stat": "ߊ߬ ߕߴߛߋ߫ ߞߐߕߐ߮ \"$1\" ߟߌ߬ߤߟߊ ߞߊ߬ߙߊ߲߬ ߠߊ߫.",
        "lockmanager-notlocked": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߐ߲߬ ߠߊ߫ \"$1\" ߟߊߞߊ߬ ߟߊ߫߸ ߊ߬ ߛߐ߰ߣߍ߲߬ ߕߍ߫.",
        "lockmanager-fail-closelock": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߋ߫ ߟߊ߫ \"$1\" ߞߐߕߐ߮ ߛߐ߰ߣߍ߲ ߘߊߕߎ߲߯ ߠߊ߫.",
        "lockmanager-fail-deletelock": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߋ߫ ߟߊ߫ \"$1\" ߞߐߕߐ߯ ߛߐ߰ߣߍ߲ ߖߏ߰ߛߌ߬ ߟߊ߫.",
        "license": "ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߦߴߌ ߘߐ߫:",
        "license-header": "ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߦߴߌ ߘߐ߫",
        "nolicense": "ߊ߬ ߡߊ߫ ߓߊߕߐ߬ߡߐ߲߬",
+       "licenses-edit": "ߕߌ߰ߦߊ ߢߣߊߕߊߟߌ ߡߊߦߟߍ߬ߡߊ߲߫",
        "upload_source_file": "(ߌ ߟߊ߫ ߞߐߕߐ߯ ߛߎߥߊ߲ߘߌߣߍ߲ ߠߎ߬ ߞߊ߬ ߝߘߊ߫ ߌ ߟߊ߫ ߕߟߋ߬ߓߊ߮ ߟߊ߫)",
        "listfiles-delete": "ߊ߬ ߖߏ߬ߛߌ߬",
        "listfiles-summary": "ߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲ ߣߌ߲߬ ߦߴߌ ߟߊ߫ ߞߐߕߐ߯ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬ ߓߍ߯ ߟߋ߬ ߦߌ߬ߘߊ߬ ߟߊ߫",
        "editcomment": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߟߊ߬ߘߛߏ߬ߟߌ ߕߘߍ߬ ߦߋ߫: <em>$1</em>",
        "changecontentmodel": "ߞߐߜߍ ߣߌ߲߬ ߞߣߐߘߐ ߛߎ߮ߦߊ ߡߊߝߊ߬ߟߋ߲߬",
        "changecontentmodel-legend": "ߞߣߐߘߐ ߛߎ߯ߦߊ ߡߊߝߊ߬ߟߋ߲߬",
-       "changecontentmodel-title-label": "ߞߐߜߍ ߞߎ߲߬ߕߐ߮",
+       "changecontentmodel-title-label": "ߞߐߜߍ ߞߎ߲߬ߕߐ߮ ߟߎ߬:",
        "changecontentmodel-current-label": "ߕߋ߲߭ߕߋ߲߭ ߞߣߐߘߐ ߛߎ߯ߦߊ:",
-       "changecontentmodel-model-label": "ߞߣߐߘߐ߫ ߛߎ߯ߦߊ߫ ߞߎߘߊ",
+       "changecontentmodel-model-label": "ߞߣߐߘߐ߫ ߛߎ߯ߦߊ߫ ߞߎߘߊ:",
        "changecontentmodel-reason-label": "ߊ߬ ߛߊߓߎ:",
        "changecontentmodel-submit": "ߊ߬ ߡߊߦߟߍ߬ߡߊ߲߫",
        "changecontentmodel-success-title": "ߞߣߐߘߐ ߛߎ߯ߦߊ ߓߘߊ߫ ߡߊߦߟߍ߬ߡߊ߲߫",
        "protect-cantedit": "ߌ ߕߍ߫ ߣߊ߬ ߛߋ߫ ߟߊ߫ ߞߐߜߍ ߣߌ߲߬ ߟߊ߬ߞߊ߲߬ߘߊ߬ߟߌ ߞߊߓߋ ߡߊߝߊ߬ߟߋ߲߬ ߠߊ߫߸ ߞߵߊ ߞߵߊ߬ ߡߊߛߐ߬ߘߐ߲߬ ߊ߬ ߡߊߦߟߍ߬ߡߊ߲ ߠߊߘߤߊ߬ߣߍ߲߬ ߕߴߌ ߦߋ߫.",
        "protect-othertime": "ߕߎ߬ߡߊ߬ ߜߘߍ:",
        "protect-othertime-op": "ߕߎ߬ߡߊ߬ ߜߘߍ߫",
+       "protect-existing-expiry": "ߕߋ߲߭ߕߋ߲߭ ߛߕߊߝߊ߫ ߕߎߡߊ: $3߸ $2",
        "protect-existing-expiry-infinity": "ߕߋ߲߭ߕߋ߲߭ ߛߕߊߝߊ߫ ߕߎߡߊ: ߘߊ߲߬ߓߊߟߌ",
        "protect-otherreason": "ߞߎ߲߬ ߡߊߞߊ߬ߝߏ߬ߕߊ߬/ߜߘߍ:",
        "protect-otherreason-op": "ߞߎ߲߬ ߜߘߍ ߟߎ߬",
        "protect-edit-reasonlist": "ߟߊ߬ߞߊ߲߬ߘߊ߬ߟߌ ߞߎ߲߭ ߡߊߦߟߍ߬ߡߊ߲߫",
        "protect-expiry-options": "ߕߎ߬ߡߊ߬ߙߋ߲߬ ߁: 1 hour, ߕߟߋ߬ ߁: 1 day, ߞߎ߲߬ߢߐ߲߰ ߁:1 week, ߞߎ߲߬ߢߐ߮ ߂:2 weeks, ߞߊߙߏ߫ ߁:1 month, ߞߊߙߏ߫ ߃:3 months, ߞߊߙߏ߫ ߆:6 months, ߛߊ߲߬ ߁:1 year, ߘߊ߲߬ߓߊߟߌ: infinite",
+       "restriction-type": "ߘߌ߬ߢߍ߬ߟߊ߬ߢߌ߬ߣߌ߲:",
        "minimum-size": "ߘߎ߰ߟߊ߬ߘߐ߬ ߢߊ߲ߞߊ߲",
        "maximum-size": "ߢߊ߲ߞߊ߲ ߞߐߘߊ߲:",
        "restriction-edit": "ߊ߬ ߡߊߦߟߍ߬ߡߊ߲߬",
        "immobile-target-namespace": "ߞߐߜߍ ߕߍ߫ ߛߐ߲߬ ߟߊߕߊ߯ ߟߊ߫ ߕߐ߯ߛߓߍ ߞߣߍ \"$1\" ߘߐ߫.",
        "immobile-target-namespace-iw": "ߥߞߌߣߌߢߐ߲߯ߕߍ ߛߘߌ߬ߜߋ߲ ߕߍ߫ ߞߏ߲߰ ߓߍ߲߬ߣߍ߲߬ ߘߌ߫ ߞߊ߬ ߞߐߜߍ ߟߎ߬ ߛߋ߲߬ߓߐ߫.",
        "immobile-source-page": "ߞߐߜߍ ߕߍ߫ ߛߋ߲߬ߓߐ߬ߕߊ߫ ߘߌ߫.",
+       "immobile-target-page": "ߊ߬ ߕߴߛߋ߫ ߟߊߕߊ߯ ߟߊ߫ ߦߋ߲߬.",
+       "movepage-invalid-target-title": "ߕߐ߯ ߡߊߢߌߣߌ߲ߣߍ߲ ߓߍ߲߬ߣߍ߲߬ ߕߍ߫.",
        "imagenocrossnamespace": "ߞߐߕߐ߮ ߕߴߛߋ߫ ߟߥߊ߫ ߟߊ߫ ߕߐ߯ߛߓߍ ߞߣߍ ߕߍ߫ ߞߐߜߍ ߡߍ߲ ߠߎ߬ ߟߊ߫ ߘߐ߫.",
+       "nonfile-cannot-move-to-file": "ߞߐߕߐ߯ߦߊߓߊߟߌ ߕߴߛߋ߫ ߟߥߊ߫ ߟߊ߫ ߞߐߕߐ߮ ߟߎ߬ ߟߊ߫ ߕߐ߯ߛߓߍ߫ ߞߣߍ ߘߐ߫.",
+       "imagetypemismatch": "ߞߐߕߐ߯ ߞߎߘߊ ߘߐߥߙߊ߬ߟߌ ߡߊ߫ ߛߐ߲߬ ߟߊߓߏ߬ߙߌ߬ ߟߴߊ߬ ߛߎ߯ߦߊ ߞߊ߲߬.",
        "imageinvalidfilename": "ߞߐߕߐ߮ ߕߐ߮ ߞߏ߲߭ ߓߍ߲߬ߣߍ߲߬ ߕߍ߫.",
+       "fix-double-redirects": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߛߎ_ߎ߯_ߛߎ߫ ߞߎ߲߬ߛߌ߲߬ߣߍ߲߬ ߦߴߊ߬ ߞߎ߲߬ߕߐ߮ ߓߐߛߎ߲ ߡߊ߬߸ ߏ߬ ߟߎ߬ ߓߍ߯ ߟߏ߲ߘߐߦߊ߫",
+       "move-leave-redirect": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߟߊߝߟߌ߬ ߌ ߞߘߐ߫",
        "export": "ߞߐߜߍ ߟߎ߬ ߟߊߝߏ߬ߦߌ߬",
        "exportall": "ߞߐߜߍ ߓߍ߯ ߟߊߝߏ߬ߦߌ߬",
        "export-submit": "ߟߊ߬ߝߏ߬ߦߌ߬ߟߌ",
        "version-ext-colheader-description": "ߕߐ߯ߟߊߘߏ߲",
        "version-ext-colheader-credits": "ߛߓߍߦߟߊ",
        "version-license-title": "$1 ߕߌ߰ߦߊ",
+       "version-poweredby-others": "ߘߏ߫ ߜߘߍ ߟߎ߬",
+       "version-poweredby-translators": "translatewiki.net ߘߟߊߡߌߘߊߟߊ߲",
+       "version-credits-summary": "ߊ߲ ߧߴߊ߬ ߝߍ߬ ߞߊ߬ ߡߐ߱ ߢߌ߲߬ ߠߎ߬ ߡߊߟߐ߲߫ ߞߊ߬ ߓߍ߲߬ ߊ߬ߟߎ߬ ߟߊ߫ ߓߟߏߓߌߟߊߢߐ߲߯ߞߊ߲ ߡߊ߬ [[Special:Version|MediaWiki]] ߘߐ߫.",
        "version-software": "ߛߎ߲ߝߘߍ ߓߘߊ߫ ߡߊߞߍ߫",
        "version-software-product": "ߥߟߏߒߘߐ",
        "version-software-version": "ߦߌߟߡߊ",
        "fileduplicatesearch-summary": "ߞߐߕߐ߯ ߓߊߟߌߣߍ߲ ߠߎ߬ ߢߌߣߌ߲߫ ߡߍ߲ ߠߎ߬ ߓߌ߲ߓߌ߲ߣߍ߲߫ ߦߋ߫ ߢߋߙߋ߲ߞߎߟߌ ߡߐ߬ߟߐ߲ ߡߊ߬.",
        "fileduplicatesearch-filename": "ߞߐߕߐ߮ ߕߐ߮:",
        "fileduplicatesearch-submit": "ߢߌߣߌ߲ߠߌ߲",
+       "fileduplicatesearch-result-1": "ߓߊߟߌߟߌ߫ ߡߊߟߐ߲ߣߍ߲߫ ߛߌ߫ ߕߍ߫ ߞߐߕߐ߮  \"$1\" ߡߊ߬.",
+       "fileduplicatesearch-noresults": "ߞߐߕߐ߯ ߛߌ߫ ߕߐ߯ ߡߊ߫ ߦߋ߫ ߞߏ߫  \"$1\".",
        "specialpages": "ߘߎ߲߬ߘߎ߬ߡߊ߬ ߞߐߜߍ ߟߎ߬",
+       "specialpages-group-other": "ߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲ ߕߐ߭ ߟߎ߬",
+       "specialpages-group-login": "ߌ ߜߊ߲߬ߞߎ߲߬/ߖߊ߬ߕߋ߬ߘߊ ߘߏ߫ ߟߊߞߊ߬",
+       "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": "ߞߐߜߍ߫ ߘߐߞߏߟߏ߲",
        "tag-filter": "[[Special:Tags|ߞߊ߲ߠߊߛߓߍ]] ߢߡߊߘߏ߲߰ߣߍ߲",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|ߡߊ߬ߛߙߋ |ߡߊ߬ߛߙߋ ߟߎ߬ }}]]: $2",
        "tags-active-yes": "ߐ߲߬ߐ߲߬ߐ߲߫",
index 384e7ae..04fec53 100644 (file)
                        "Railfail536",
                        "Vlad5250",
                        "CiaPan",
-                       "BadDog"
+                       "BadDog",
+                       "Rail"
                ]
        },
        "tog-underline": "Podkreślenie linków:",
        "undo-norev": "Edycja nie może być cofnięta, ponieważ nie istnieje lub została usunięta.",
        "undo-nochange": "Wygląda na to, że edycja została już anulowana.",
        "undo-summary": "Anulowanie wersji $1 autorstwa [[Special:Contributions/$2|$2]] ([[User talk:$2|dyskusja]])",
+       "undo-summary-anon": "Anulowanie wersji $1 autorstwa [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Anulowanie wersji $1 autorstwa ukrytego użytkownika",
        "cantcreateaccount-text": "Tworzenie konta z tego adresu IP ('''$1''') zostało zablokowane przez [[User:$3|$3]].\n\nPodany przez $3 powód to ''$2''",
        "cantcreateaccount-range-text": "Tworzenie konta z adresów IP w zakresie <strong>$1</strong>, zawierającego twój adres IP (<strong>$4</strong>), zostało zablokowane przez [[User:$3|$3]].\n\nPodany przez $3 powód to <em>$2</em>",
        "alreadyrolled": "Nie można dla strony [[:$1|$1]] cofnąć ostatniej zmiany, którą wykonał [[User:$2|$2]] ([[User talk:$2|dyskusja]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]).\nKtoś inny zdążył już to zrobić lub wprowadził własne poprawki do treści strony.\n\nAutorem ostatniej zmiany jest teraz [[User:$3|$3]] ([[User talk:$3|dyskusja]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Edycję opisał: <em>$1</em>.",
        "revertpage": "Wycofano edycje użytkownika [[Special:Contributions/$2|$2]] ([[User talk:$2|dyskusja]]). Autor przywróconej wersji to [[User:$1|$1]].",
+       "revertpage-anon": "Wycofano edycje użytkownika [[Special:Contributions/$2|$2]]. Autor przywróconej wersji to [[User:$1|$1]].",
        "revertpage-nouser": "Wycofano edycje ukrytego użytkownika. Autor przywróconej wersji to {{GENDER:$1|[[User:$1|$1]]}}.",
        "rollback-success": "Wycofano edycje {{GENDER:$3|użytkownika|użytkowniczki}} $1;\nprzywrócono ostatnią wersję autorstwa {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Błąd sesji",
index bf58c17..1106f5f 100644 (file)
        "undo-norev": "A edição não pôde ser desfeita porque não existe ou foi apagada.",
        "undo-nochange": "Parece que a edição já foi desfeita.",
        "undo-summary": "Desfeita a edição $1 de [[Special:Contributions/$2|$2]] ([[User talk:$2|Discussão]])",
+       "undo-summary-anon": "Desfazer revisão $1 por [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Desfazer a revisão $1 de um usuário oculto",
        "cantcreateaccount-text": "Este IP ('''$1''') foi bloqueado de criar novas contas por [[User:$3|$3]].\n\nA justificativa apresentada por $3 foi ''$2''",
        "cantcreateaccount-range-text": "A criação de conta a partir dos endereços IP no intervalo <strong>$1</strong>, que inclui o seu endereço IP (<strong>$4</strong>), foi bloqueada por [[User:$3|$3]].\n\nA razão dada por $3 é <em>$2</em>",
        "backend-fail-contenttype": "Não foi possível determinar o tipo de conteúdo do arquivo para armazenar em \"$1\".",
        "backend-fail-batchsize": "O servidor de armazenamento retornou um conjunto de $1 {{PLURAL:$1|operação|operações}} de arquivo, enquanto seu limite é de $2 {{PLURAL:$1|operação|operações}}.",
        "backend-fail-usable": "Não foi possível ler ou salvar o arquivo $1 devido a permissões insuficientes a diretórios, ou a repositórios/diretórios inexistentes.",
+       "backend-fail-stat": "Não foi possível ler o estado do arquivo \"$1\".",
+       "backend-fail-hash": "Não foi possível determinar o resumo criptográfico do arquivo \"$1\".",
        "filejournal-fail-dbconnect": "Não foi possível se conectar ao banco de dados de registros do sistema de armazenamento \"$1\".",
        "filejournal-fail-dbquery": "Não foi possível atualizar o banco de dados de registros do sistema de armazenamento \"$1\".",
        "lockmanager-notlocked": "Não foi possível desbloquear \"$1\" por ele não se encontrar bloqueado.",
        "alreadyrolled": "Não foi possível reverter a última edição de [[:$1]] por [[User:$2|$2]] ([[User talk:$2|discussão]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nalguém já editou ou reverteu a página.\n\nA última edição da página foi feita por [[User:$3|$3]] ([[User talk:$3|discussão]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "O sumário de edição era: <em>$1</em>.",
        "revertpage": "Foram revertidas as edições de [[Special:Contributions/$2|$2]] ([[User talk:$2|disc]]) para a última versão por [[User:$1|$1]]",
+       "revertpage-anon": "Foram revertidas as edições de [[Special:Contributions/$2|$2]] para a última versão por [[User:$1|$1]]",
        "revertpage-nouser": "Revertidas as edições de um usuário oculto para a última revisão de {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Edições revertidas por {{GENDER:$3|$1}};\nalterado para a última revisão por {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Erro de sessão",
index e550e4a..c728421 100644 (file)
        "nocreate-loggedin": "Não tem permissão para criar páginas novas.",
        "sectioneditnotsupported-title": "Edição de secções não suportada",
        "sectioneditnotsupported-text": "A edição de secções não é suportada nesta página.",
+       "modeleditnotsupported-title": "Não é suportada a edição",
+       "modeleditnotsupported-text": "Não é suportada a edição do modelo de conteúdo $1.",
        "permissionserrors": "Erro de permissão",
        "permissionserrorstext": "Não tem permissão para fazer isso, {{PLURAL:$1|pelo seguinte motivo|pelos seguintes motivos}}:",
        "permissionserrorstext-withaction": "Não tem permissão para $2, {{PLURAL:$1|pelo seguinte motivo|pelos seguintes motivos}}:",
        "content-model-json": "JSON",
        "content-json-empty-object": "Objeto vazio",
        "content-json-empty-array": "Matriz vazia",
+       "unsupported-content-model": "<strong>Aviso:</strong> O modelo de conteúdo $1 não é suportado nesta wiki.",
+       "unsupported-content-diff": "As diferenças entre edições não são suportadas para o modelo de conteúdo $1.",
+       "unsupported-content-diff2": "As diferenças entre edições dos modelos de conteúdo $1 e $2 não são suportadas nesta wiki.",
        "deprecated-self-close-category": "Páginas com etiquetas HTML autofechadas inválidas",
        "deprecated-self-close-category-desc": "Esta página contém etiquetas HTML autofechadas, que são inválidas, tais como <code>&lt;b/></code> e <code>&lt;span/></code>. O comportamento destas etiquetas será alterado em breve, para ser consistente com a especificação HTML5, pelo que o seu uso no texto wiki foi descontinuado.",
        "duplicate-args-warning": "<strong>Aviso:</strong> [[:$1]] chama [[:$2]] com mais de um valor para o parâmetro \"$3\". Somente o último valor fornecido será utilizado.",
        "backend-fail-contenttype": "Não foi possível determinar o tipo de conteúdo do ficheiro para armazenar em \"$1\".",
        "backend-fail-batchsize": "Foi fornecido um bloco de $1 {{PLURAL:$1|operação|operações}} sobre ficheiros ao servidor de armazenamento; o limite é de $2 {{PLURAL:$2|operação|operações}}.",
        "backend-fail-usable": "Não foi possível ler ou gravar o ficheiro \"$1\" devido a permissões insuficientes ou a diretórios/repositórios inexistentes.",
+       "backend-fail-stat": "Não foi possível ler o estado do ficheiro \"$1\".",
+       "backend-fail-hash": "Não foi possível determinar o resumo criptográfico do ficheiro \"$1\".",
        "filejournal-fail-dbconnect": "Não foi possível estabelecer ligação à base de dados de registos no servidor de armazenamento \"$1\".",
        "filejournal-fail-dbquery": "Não foi possível atualizar a base de dados de registos do servidor de armazenamento \"$1\".",
        "lockmanager-notlocked": "Não foi possível desbloquear \"$1\" porque não se encontra bloqueado.",
        "sessionfailure": "Foram detetados problemas com a sua sessão;\na operação foi cancelada como medida de proteção contra a intercetação de sessões.\nReenvie o formulário, por favor.",
        "changecontentmodel": "Alterar modelo de conteúdo de uma página",
        "changecontentmodel-legend": "Editar modelo de contéudo",
-       "changecontentmodel-title-label": "Título da página",
+       "changecontentmodel-title-label": "Título da página:",
        "changecontentmodel-current-label": "Modelo de conteúdo atual:",
-       "changecontentmodel-model-label": "Novo modelo de conteúdo",
+       "changecontentmodel-model-label": "Novo modelo de conteúdo:",
        "changecontentmodel-reason-label": "Motivo:",
        "changecontentmodel-submit": "Alterar",
        "changecontentmodel-success-title": "O modelo de conteúdo foi alterado",
index 50b0658..a33608f 100644 (file)
                        "Woytecr",
                        "PiefPafPier",
                        "Bagas Chrisara",
-                       "Waldyrious"
+                       "Waldyrious",
+                       "Ktrst"
                ]
        },
        "sidebar": "{{notranslate}}",
        "tog-useeditwarning": "Used as label for the checkbox in [[Special:Preferences#mw-prefsection-editing|Special:Preferences]].",
        "tog-prefershttps": "Toggle option used in [[Special:Preferences]] that indicates if the user wants to use a secure connection when logged in",
        "tog-showrollbackconfirmation": "Toggle option used in [[Special:Preferences]] to enable/disable rollback confirmation prompt. Should be visible only to users with rollback rights.",
+       "tog-requireemail": "Toggle option used in [[Special:Preferences]].  Should be only visible to users who have confirmed their email address.\n\nSee also: {{msg-mw|prefs-help-requireemail}}",
        "underline-always": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"always underline links\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Always}}",
        "underline-never": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"never underline links\", there are also options {{msg-mw|Underline-always}} and {{msg-mw|Underline-default}}.\n\n{{Gender}}\n{{Identical|Never}}",
        "underline-default": "Used in [[Special:Preferences#mw-prefsection-rendering|Preferences]].\n\nThis option means \"underline links as in your user skin or your browser\", there are also options {{msg-mw|Underline-never}} and {{msg-mw|Underline-always}}.\n\n{{Gender}}\n{{Identical|Browser default}}",
        "undo-main-slot-only": "Message appears if an attempt to revert an edit by clicking the \"undo\" link on the page history fails because it involves content outside the page's main slot, which is not yet supported.\nThis message is temporary, see https://phabricator.wikimedia.org/T189808\n\nSee also:\n* {{msg-mw|Undo-failure}}\n{{Identical|Undo}}",
        "undo-norev": "Message appears if an attempt to revert an edit by clicking the \"undo\" link on the page history fails.\n\nSee also:\n* {{msg-mw|Undo-failure}}\n* {{msg-mw|Undo-nochange}}\n{{Identical|Undo}}",
        "undo-nochange": "Message appears if an attempt to revert an edit by clicking the \"undo\" link results in an edit making no change to the current version of the page.\n\nSee also:\n* {{msg-mw|Undo-failure}}\n* {{msg-mw|Undo-norev}}",
-       "undo-summary": "Edit summary for an undo action. Parameters:\n* $1 - revision ID\n* $2 - username\n{{Identical|Undo}}",
+       "undo-summary": "Edit summary for an undo action. Parameters:\n* $1 - revision ID\n* $2 - username\n{{Identical|Undo}}\nSee also:\n* {{msg-mw|Undo-summary-anon}}",
+       "undo-summary-anon": "Edit summary for an undo action, when undoing an edit by an anonymous user and $wgDisableAnonTalk is activated. Parameters:\n* $1 - revision ID\n* $2 - username\nSee also:\n* {{msg-mw|Undo-summary}}",
        "undo-summary-username-hidden": "Edit summary for an undo action where the username of the old revision is hidden.\n\nParameters:\n* $1 - the revision ID being undone\nSee also:\n* {{msg-mw|Undo-summary}}",
        "cantcreateaccount-text": "Used as error message when account creation is prevented by an IP block.\n* $1 - target IP address\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\nSee also:\n* {{msg-mw|Cantcreateaccount-range-text}}",
        "cantcreateaccount-range-text": "Used instead of the {{msg-mw|Cantcreateaccount-text}} when the block is a range block.\n* $1 - target IP address range\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\n* $4 - current user's IP address",
        "prefs-help-email": "Shown as explanation text on [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* {{msg-mw|prefs-changeemail|link title}}\n* {{msg-mw|prefs-setemail|link title}}",
        "prefs-help-email-others": "This text is shown on account creation, below the description of the e-mail address field (which is optional).\n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* {{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-changeemail|link title}}\n* {{msg-mw|prefs-setemail|link title}}",
        "prefs-help-email-required": "Shown as explanation text on [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nSee also:\n* {{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* {{msg-mw|prefs-changeemail|link title}}\n* {{msg-mw|prefs-setemail|link title}}",
+       "prefs-help-requireemail": "Shown as explanation text on [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nSee also: {{msg-mw|tog-requireemail}}",
        "prefs-info": "Header for the box giving basic information on the user account, displayed on the 'user profile' tab of the [[Special:Preferences|user preferences]] special page.\n{{Identical|Basic information}}",
        "prefs-i18n": "Field set legend for user preferences regarding the interface language",
        "prefs-signature": "{{Identical|Signature}}",
        "cantrollback": "Used as error message when rollback fails due to there not being a valid revision to revert back to.\n\nSee also:\n* {{msg-mw|Notvisiblerev}}\n{{Identical|Revert}}\n{{Identical|Rollback}}",
        "alreadyrolled": "Appear when there's rollback and/or edit collision.\n\nRefers to:\n* {{msg-mw|Pipe-separator}}\n* {{msg-mw|Contribslink}}\nParameters:\n* $1 - the page to be rolled back\n* $2 - the editor to be rolled-back of that page\n* $3 - the editor that cause collision\n{{Identical|Rollback}}",
        "editcomment": "Only shown if there is an edit {{msg-mw|Summary}}. Parameters:\n* $1 - the edit summary",
-       "revertpage": "Parameters:\n* $1 - username 1\n* $2 - username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from\nSee also:\n* {{msg-mw|Revertpage-nouser}}\n{{Identical|Revert}}",
-       "revertpage-nouser": "This is a confirmation message a user sees after reverting, when the username of the version is hidden with RevisionDelete.\n\nIn other cases the message {{msg-mw|Revertpage}} is used.\n\nParameters:\n* $1 - username 1, can be used for GENDER\n* $2 - (Optional) username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from",
+       "revertpage": "Parameters:\n* $1 - username 1\n* $2 - username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from\nSee also:\n* {{msg-mw|Revertpage-anon}}\n* {{msg-mw|Revertpage-nouser}}\n{{Identical|Revert}}",
+       "revertpage-anon": "Parameters:\n* $1 - username 1\n* $2 - username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from\nSee also:\n* {{msg-mw|Revertpage}}\n* {{msg-mw|Revertpage-nouser}}\n{{Identical|Revert}}",
+       "revertpage-nouser": "This is a confirmation message a user sees after reverting, when the username of the version is hidden with RevisionDelete.\n\nIn other cases the message {{msg-mw|Revertpage}} or {{msg-mw|Revertpage-anon}} is used.\n\nParameters:\n* $1 - username 1, can be used for GENDER\n* $2 - (Optional) username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from",
        "rollback-success": "This message shows up on screen after successful revert (generally visible only to admins). Parameters:\n* $1 - user whose changes have been reverted\n* $2 - user who produced version, which replaces reverted version\n* $3 - the first user's name, can be used for GENDER\n* $4 - the second user's name, can be used for GENDER\n{{Identical|Revert}}\n{{Identical|Rollback}}",
        "sessionfailure-title": "Used as title of the error message {{msg-mw|Sessionfailure}}.",
        "sessionfailure": "Used as error message.\n\nThe title for this error message is {{msg-mw|Sessionfailure-title}}.",
        "ipblocklist-legend": "Used as legend of the form in [[Special:BlockList]].\n\nSee also:\n* {{msg-mw|Ipblocklist-legend}}\n* {{msg-mw|Ipblocklist-submit}}",
        "blocklist-userblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
        "blocklist-tempblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
+       "blocklist-indefblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
        "blocklist-addressblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
        "blocklist-type": "Used as label for dropdown box in [[Special:BlockList]].",
        "blocklist-type-opt-all": "Used as option for dropdown box in [[Special:BlockList]]. This is the default option and indicates that \"all\" blocks will be listed\n{{Identical|All}}",
index 3bcb1fd..d6094cd 100644 (file)
        "rcfilters-liveupdates-button-title-off": "Показывать новые изменения сразу после их появления",
        "rcfilters-watchlist-markseen-button": "Отметить все изменения как просмотренные",
        "rcfilters-watchlist-edit-watchlist-button": "Редактировать ваш список наблюдения",
-       "rcfilters-watchlist-showupdated": "Ð\98зменениÑ\8f Ñ\81Ñ\82Ñ\80аниÑ\86, ÐºÐ¾Ñ\82оÑ\80Ñ\8bе Ð²Ñ\8b Ð½Ðµ Ð¿Ð¾Ñ\81еÑ\89али Ñ\81 Ñ\82ого Ð¼Ð¾Ð¼ÐµÐ½Ñ\82а, ÐºÐ°Ðº Ð¾Ð½Ð¸ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ð»Ð¸Ñ\81Ñ\8c, Ð²Ñ\8bделенÑ\8b <strong>жиÑ\80нÑ\8bм</strong> Ð¸ Ð¾Ñ\82меÑ\87енÑ\8b Ð¿Ð¾Ð»ным маркером.",
+       "rcfilters-watchlist-showupdated": "Ð\98зменениÑ\8f Ñ\81Ñ\82Ñ\80аниÑ\86, ÐºÐ¾Ñ\82оÑ\80Ñ\8bе Ð²Ñ\8b Ð½Ðµ Ð¿Ð¾Ñ\81еÑ\89али Ñ\81 Ñ\82ого Ð¼Ð¾Ð¼ÐµÐ½Ñ\82а, ÐºÐ°Ðº Ð¾Ð½Ð¸ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ð»Ð¸Ñ\81Ñ\8c, Ð²Ñ\8bделенÑ\8b <strong>полÑ\83жиÑ\80нÑ\8bм</strong> Ñ\88Ñ\80иÑ\84Ñ\82ом Ð¸ Ð¾Ñ\82меÑ\87енÑ\8b Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½ным маркером.",
        "rcfilters-preference-label": "Использовать не JavaScript интерфейс",
        "rcfilters-preference-help": "Загружает свежие правки без поиска по фильтрам или возможностей подсветки.",
        "rcfilters-watchlist-preference-label": "Использовать интерфейс без JavaScript",
        "backend-fail-batchsize": "Хранилище получило блок из $1 {{PLURAL:$1|файловой операции|файловых операций}}, ограничение составляет $2 {{PLURAL:$1|файловую операцию|файловых операций|файловых операции}}.",
        "backend-fail-usable": "Не удалось прочитать или записать файл «$1» из-за нехватки прав или отсутствия нужных папок.",
        "backend-fail-stat": "Не удалось прочитать статус файла «$1».",
-       "backend-fail-hash": "Ð\9cожеÑ\82 определить криптографический хеш файла «$1».",
+       "backend-fail-hash": "Ð\9dевозможно определить криптографический хеш файла «$1».",
        "filejournal-fail-dbconnect": "Не удалось подключиться к базе данных журнала для хранилища «$1».",
        "filejournal-fail-dbquery": "Не удалось обновить базу данных журнала для хранилища «$1».",
        "lockmanager-notlocked": "Не удалось разблокировать «$1»; он не заблокирован.",
index 45da11d..332a02e 100644 (file)
                ]
        },
        "tog-underline": "ڳنڍڻي هيٺان لڪير:",
-       "tog-hideminor": "تازين تبديلين منجھہ معمولي تبديليون لڪايو",
+       "tog-hideminor": "تازين تبديلين ۾ معمولي سنوارون لڪايو",
        "tog-hidepatrolled": "تازيون نگرانيل تبديليون لڪايو",
-       "tog-newpageshidepatrolled": "نَوَن صفحن واري فهرست مان نگرانيل صفحا لڪايو",
+       "tog-newpageshidepatrolled": "نَوَن صفحن جي فھرست مان گشت-ڪيل صفحا لڪايو",
        "tog-hidecategorization": "صفحن جا زمرا لڪايو",
-       "tog-extendwatchlist": "تازÙ\87 ØªØ±Ù\8aÙ\86 Ø¨Ø¯Ø±Ø§Ù\86 Ø³Ù\85Ù\88رÙ\8aÙ\88Ù\86 ØªØ¨Ø¯Ù\8aÙ\84Ù\8aÙ\88Ù\86 Ú\8fÙ\8aکارڻ Ù\84اءÙ\90 Ø²Ù\8aر Ù\86ظر Ù\81Ù\87رست Ú©Ù\8a Ù\88سÙ\8aع ÚªØ±Ù\8aÙ\88.",
+       "tog-extendwatchlist": "رڳÙ\88 ØªØ§Ø²Û\81 ØªØ±Ù\8aÙ\86 Ù\86Û\81Ø\8c Ø³Ù\85Ù\88رÙ\8aÙ\88Ù\86 ØªØ¨Ø¯Ù\8aÙ\84Ù\8aÙ\88Ù\86 Ú\8fÙ\8aکارڻ Ù\84اءÙ\90 Ù\86ظر Û¾ Ù\81ھرست Ú©Ù\8a Ú¦Ú¾Ù\84اÙ\8aÙ\88",
        "tog-numberheadings": "سُرخين کي خودڪاراً نمبر ڏيو",
        "tog-editondblclick": "ٻٽي ڪلڪ تي صفحا سنواريو",
        "tog-editsectiononrightclick": "ڀاڱن جي عنوان کي رائيٽ ڪلڪ ڪري سنوارڻ واري سھولت کي ڪارگر بڻايو",
        "tog-watchmoves": "جيڪي صفحا ۽ فائيل آءُٗ چوريان، سي منهنجي نظر ۾ فھرست ۾ شامل ڪريو",
        "tog-watchdeletion": "آءُٗ جيڪي صفحا ۽ فائيل  ڊاهيان، سي منهنجي نظر ۾ فھرست تي رکو",
        "tog-watchuploads": "منھنجا نوان چاڙهيل فائيلس نظر ۾ فھرست ۾ شامل ڪريو",
-       "tog-watchrollback": "انهن صفحن کي منهنجي نظر ۾ فھرست تي رکو، جن ۾ تبديلين کي مون واپس ورايو آهي",
+       "tog-watchrollback": "انھن صفحن کي منھنجي نظر ۾ فھرست تي رکو، جتي مون واپس ورائڻ جو عمل آهي.",
        "tog-minordefault": "سمورين سنوارن کي پاڻمرادو معمولي طور نشان لڳايو",
        "tog-previewontop": "سنوار دٻيءَ مٿان پيش-نگاھ ڏيکاريو",
        "tog-previewonfirst": "پھرين سنوار تي پيش-نگاھ ڏيکاريو",
-       "tog-enotifwatchlistpages": "منهنجي نظر ۾ فھرست اندر شامل ڪنهن صفحي يا فائيل ۾ تبديل پيش اچي مون کي برقٽپال اماڻيو",
+       "tog-enotifwatchlistpages": "منھنجي نظر ۾ فھرست ۾ شامل ڪو صفحو يا فائيل بدلايو وڃي تہ مون کي برقٽپال اماڻيو",
        "tog-enotifusertalkpages": "منهنجي مباحثي صفحي ۾ تبديليءَ جي صورت ۾ مون کي برقٽپال اماڻيو",
        "tog-enotifminoredits": "صفحن ۽ فائيلن جي معمولي سنوارن بابت بہ مون کي برقٽپال ڪريو",
        "tog-enotifrevealaddr": "پڌراين ۾ منهنجو برقٽپال پتو ظاهر ڪريو",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|توھان کي}} {{PLURAL:$3|ٻي واپرائيندڙ|$3 واپرائيندڙن}} ($2) کان $1 آھن.",
        "youhavenewmessagesmanyusers": "توهان لاءِ ڪيترن ئي واپرائيندڙن ($2) طرفان $1 آهن.",
        "newmessageslinkplural": "{{PLURAL:$1|ھڪ نئون پيغام|999=نوان پيغام}}",
-       "newmessagesdifflinkplural": "آخرÙ\8a {{PLURAL:$1|تبدÙ\8aÙ\84Ù\8a|999=تبدÙ\8aÙ\84Ù\8aÙ\88Ù\86}}",
+       "newmessagesdifflinkplural": "آخرÙ\8a {{PLURAL:$1|بدÙ\84اءÙ\8f|999=بدÙ\84اءÙ\8e}}",
        "youhavenewmessagesmulti": "$1 تي توهان لاءِ نوان نياپا آهن",
        "editsection": "سنواريو",
        "editold": "سنواريو",
        "databaseerror-query": "استفسار: $1",
        "databaseerror-function": "ڪاڄ: $1",
        "databaseerror-error": "چُڪَ: $1",
-       "laggedslavemode": "<strong>Ú\86تاءÙ\8f:</strong> ØµÙ\81Ø­Ù\8a Û¾ Ú¾Ø§Ú»Ù\88ÚªÙ\8aÙ\88Ù\86 ØªØ¨Ø¯Ù\8aÙ\84Ù\8aÙ\88Ù\86 Ù\86Ù\87 Ú¾Ø¬Ú» Ø¬Ù\88 Ø§Ù\85ڪاÙ\86 Ø¢Ú¾ي.",
+       "laggedslavemode": "<strong>Ú\86تاءÙ\8f:</strong> ØµÙ\81Ø­Ù\88 Ø´Ø§Ù\8aد Ú¾Ø§Ú»Ù\88ÚªÙ\8aÙ\88Ù\86 Ø¬Ø¯ØªÙ\88Ù\86 Ù\86Û\81 Ø±Ú©Ù\86دÙ\88 Ú¾Ø¬ي.",
        "readonly": "اعدادخانو بنديل",
        "missingarticle-rev": "(ڀيرو#: $1)",
        "missingarticle-diff": "(تفاوت: $1، $2)",
        "protectedpagetext": "هيءُ صفحو سوارڻ ۽ ٻين عملن کان بچائڻ لاءِ تحفظيو ويو آهي.",
        "viewsourcetext": "توهان هن صفحي جو ڪوڊ ڏسي ۽ نقل ڪري سگھو ٿا.",
        "viewyourtext": "توھان ھاڻي ھن صفحي ۾ <strong>پنھنجي سنوارن</strong> جو ذريعو ڏسي ۽ نقل ڪري سگھو ٿا.",
-       "protectedinterface": "هي صفحو سافٽ ويئر جو انٽرفيس متعين ڪري ٿو ۽ غلط استعال کان بچڻ لاءِ ان کي تحفظيو ويو آهي.\nتمام وڪي ۾ ترجمو شامل ڪرڻ لاءِ يا هن ۾ تبديلي ڪرڻ لاءِ ميڊياوڪي ترجمو [https://translatewiki.net/ translatewiki.net] استعمال ڪيو.",
+       "protectedinterface": "هي صفحو سافٽ ويئر جو انٽرفيس متعين ڪري ٿو ۽ غلط استعال کان بچڻ لاءِ ان کي تحفظيو ويو آهي.\nتمام وڪين ۾ ترجمو وجھڻ يا بدلائڻ لاءِ، مھرباني ڪري [https://translatewiki.net/ translatewiki.net] استعمال ڪريو، ميڊياوڪي جي لوڪلائيزيشڻ رٿا.",
        "namespaceprotected": "توهان کي نانءُپولار <strong>$1</strong> جا صفحا سنوارڻ جا اختيار ناهن.",
        "sitecssprotected": "اوهان وٽ ھن سيايسايس صفحي کي سنوارڻ جي اجازت ناھي، ڇو تہ ان سان سڀني گھمندڙ متاثر ٿي سگھن ٿا.",
        "mycustomcssprotected": "توهان کي هيءُ CSS صفحو سنوارڻ جي اجازت نہ آهي.",
        "password-name-match": "توهان جو ڳجھولفظ توهان جي واپرائيندڙ-نانءَ کان مختلف هجڻ لازمي آھي.",
        "mailmypassword": "ڳجھولفظ ٻيھر مقرر ڪريو",
        "passwordremindertitle": "{{SITENAME}} لاءِ نئون عارضي ڳجھولفظ",
-       "passwordremindertext": "ÚªÙ\86Ú¾Ù\86 (آءÙ\90Ù\8a Ù¾ØªÙ\8a $1 ØªØ§Ù\86) Ø§Ø³Ø§Ù\86 Ú©Ù\8a {{SITENAME}} ($4) Ù\84اءÙ\90 Ù\86ئÙ\88Ù\86 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ø§Ù\85اڻڻ Ø¬Ù\8a Ú¯Ù\8fھرÙ\8e ÚªØ¦Ù\8a.\"$2\" Ù\88اپرائÙ\8aÙ\86دÚ\99 Ù\84اءÙ\90 Ú¾Úª Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ø³Ø±Ø¬Ù\8aÙ\88 Ù\88Ù\8aع Ø¢Ù\87Ù\8a \"$3\" ØªÙ\8a ØªØ±ØªÙ\8aب Ú\8fÙ\86Ù\88 Ù\88Ù\8aÙ\88 Ú¾Ù\88. Ø¬Ù\8aÚªÚ\8fÚ¾Ù\86 Ø§Ú¾Ù\88 ØªÙ\88ھاÙ\86 Ø¬Ù\88 Ø§Ø±Ø§Ø¯Ù\88 Ú¾Ù\8aÙ\88Ø\8c ØªÛ\81 Ú¾Ø§Ú»Ù\8a ØªÙ\88ھاÙ\86 Ú©Ù\8a Ú¾Ù\8aÙ\86ئر Ø¦Ù\8a Ø¯Ø§Ø®Ù\84 Ù¿Ù\8a Ù¾Ù\86Ú¾Ù\86جÙ\88 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ ØªØ¨Ø¯Ù\8aÙ\84 ÚªØ±Ú» Ú¯Ú¾Ø±Ø¬Ù\8a.\nتÙ\88ھاÙ\86 Ø¬Ù\88 Ø¹Ø§Ø±Ø¶Ù\8a Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ {{PLURAL:$5|Ù\87Úª Ú\8fÙ\8aÙ\86Ú¾Ù\8fÙ\86|$5 Ú\8fÙ\8aÙ\86Ú¾Ù\8eÙ\86}} Û¾ Ø®ØªÙ\85 Ù¿Ù\8aÙ\86دÙ\88.\n\nجÙ\8aÚªÚ\8fÚ¾Ù\86 Ø§Ú¾Ø§ Ú¯Ù\8fھرÙ\8e Ø§Ù\88ھاÙ\86 Ù\86Û\81 ÚªØ¦Ù\8a Ú¾Ø¦Ù\8aØ\8c Ù\8aا Ú¾Ø§Ú»Ù\8a Ø§Ù\88ھاÙ\86 Ú©Ù\8a Ù¾Ù\86Ú¾Ù\86جÙ\88 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ù\8aاد Ø§Ú\86Ù\8a Ù\88Ù\8aÙ\88 Ø¢Ú¾Ù\8a Û½ ØªÙ\88ھاÙ\86 Ø§Ù\86 Ú©Ù\8a ØªØ¨Ø¯Ù\8aÙ\84 ÚªØ±ڻ نٿا چاھيو، تہ توھان ھن نياپي کي نظر انداز ڪندي پنھنجو پراڻو ڳجھولفظ ئي استعمال ڪري سگھو ٿا.",
+       "passwordremindertext": "ÚªÙ\86Ú¾Ù\86 (آئÙ\90Ù¾Ù\8a Ù¾ØªÙ\8a $1 ØªØ§Ù\86) Ø§Ø³Ø§Ù\86 Ú©Ù\8a {{SITENAME}} ($4) Ù\84اءÙ\90 Ù\86ئÙ\88Ù\86 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ø§Ù\85اڻڻ Ø¬Ù\8a Ú¯Ù\8fھرÙ\8e ÚªØ¦Ù\8a.\"$2\" Ù\88اپرائÙ\8aÙ\86دÚ\99 Ù\84اءÙ\90 Ú¾Úª Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ø³Ø±Ø¬Ù\8aÙ\88 Ù\88Ù\8aÙ\88 Ø¢Ù\87Ù\8a \"$3\" ØªÙ\8a ØªØ±ØªÙ\8aب Ú\8fÙ\86Ù\88 Ù\88Ù\8aÙ\88 Ú¾Ù\88. Ø¬Ù\8aÚªÚ\8fÚ¾Ù\86 Ø§Ú¾Ù\88 ØªÙ\88ھاÙ\86 Ø¬Ù\88 Ø§Ø±Ø§Ø¯Ù\88 Ú¾Ù\8aÙ\88Ø\8c ØªÛ\81 Ú¾Ø§Ú»Ù\8a ØªÙ\88ھاÙ\86 Ú©Ù\8a Ú¾Ù\8aÙ\86ئر Ø¦Ù\8a Ø¯Ø§Ø®Ù\84 Ù¿Ù\8a Ù¾Ù\86Ú¾Ù\86جÙ\88 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ø¨Ø¯Ù\84ائڻ Ú¯Ú¾Ø±Ø¬Ù\8a.\nتÙ\88ھاÙ\86 Ø¬Ù\88 Ø¹Ø§Ø±Ø¶Ù\8a Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ {{PLURAL:$5|Ù\87Úª Ú\8fÙ\8aÙ\86Ú¾Ù\8fÙ\86|$5 Ú\8fÙ\8aÙ\86Ú¾Ù\8eÙ\86}} Û¾ Ø®ØªÙ\85 Ù¿Ù\8aÙ\86دÙ\88.\n\nجÙ\8aÚªÚ\8fÚ¾Ù\86 Ø§Ú¾Ø§ Ú¯Ù\8fھرÙ\8e Ø§Ù\88ھاÙ\86 Ù\86Û\81 ÚªØ¦Ù\8a Ú¾Ø¦Ù\8aØ\8c Ù\8aا Ú¾Ø§Ú»Ù\8a Ø§Ù\88ھاÙ\86 Ú©Ù\8a Ù¾Ù\86Ú¾Ù\86جÙ\88 Ú³Ø¬Ú¾Ù\88Ù\84Ù\81ظ Ù\8aاد Ø§Ú\86Ù\8a Ù\88Ù\8aÙ\88 Ø¢Ú¾Ù\8a Û½ ØªÙ\88ھاÙ\86 Ø§Ù\86 Ú©Ù\8a Ø¨Ø¯Ù\84ائڻ نٿا چاھيو، تہ توھان ھن نياپي کي نظر انداز ڪندي پنھنجو پراڻو ڳجھولفظ ئي استعمال ڪري سگھو ٿا.",
        "noemail": "واپرائيندڙ \"$1\" جو ڪو بہ برقٽپال پتو درج ٿيل ناهي.",
        "noemailcreate": "توھان کي قابلڪار برقٽپال پتو مھيا ڪرڻو پوندو.",
        "passwordsent": "واپرائيندڙ \"$1\" لاءِ ھڪ نئون ڳجھولفظ برقٽپال ذريعي اماڻيو ويو آهي.\nمھرباني ڪري اھو حاصل ڪرڻ بعد داخل ٿيندا.",
        "pt-userlogout": "خارج ٿيو",
        "php-mail-error-unknown": "پي ايڇ پي جي  ڪاڄ() اندر اڻڄاتل چُڪَ.",
        "user-mail-no-addy": "برقٽپال پتو ڄاڻائڻ کان سواءِ برقٽپال اماڻڻ جي ڪوشش ڪئي وئي.",
-       "changepassword": "ڳجھÙ\88Ù\84Ù\81ظ ØªØ¨Ø¯Ù\8aÙ\84 ÚªØ±يو",
+       "changepassword": "ڳجھÙ\88Ù\84Ù\81ظ Ø¨Ø¯Ù\84ايو",
        "resetpass_announce": "داخل ٿيڻ جو عمل پورو ڪرڻ لاءِ، توهان کي نئون ڳجھولفظ اختيار مقرر ڪرڻو پوندو.",
        "resetpass_header": "کاتي جو ڳجھولفظ بدلايو",
        "oldpassword": "اڳوڻو ڳجھولفظ:",
        "showdiff": "تبديليون ڏيکاريو",
        "blankarticle": "<strong>چِتاءُ:</strong> اوهان خالي صفحو سرجي رهيا آهيو.\nجيڪڏهن اوهان «$1» تي ٻيھر ڪلڪ ڪيو، تہ هي صفحو بغير ڪنھن مواد جي سرجيو ويندو.",
        "anoneditwarning": "<strong>چِتاءُ:</strong> توھان داخل ٿيل نہ آھيو. توھان جو آءِپي پتو عوامي طور ظاھر ٿيندو جي توھان ڪي سنوارون ڪريو ٿا. جيڪڏھن توھان <strong>[$1 داخل ٿيو]</strong> ٿا يا <strong>[$2 کاتو کوليو]</strong> ٿا، تہ ٻين فائدن سان گڏ توھان جون سنوارون توھان جي واپرائيندڙ-نانءَ سان منسوب ڪيون وينديون.",
-       "anonpreviewwarning": "توهان داخل ٿيل نہ آهيو. جيڪڏهن توهان صفحي ۾ تبديليون سانڍيون تہ اهڙين تبديلين ساڻ توهان جو آءِپي پتو درج ڪيو ويندو.",
-       "missingcommenttext": "براءِ مھرباني ڪو تاثر درج ڪندا.",
+       "anonpreviewwarning": "<em>توهان داخل ٿيل نہ آهيو. سانڍڻ سان توهان جو آئِپي پتو ھن صفحي جي سنوار سوانح ۾ درج ٿيندو.</em>",
+       "missingcommenttext": "مھرباني ڪري ڪو تاثر داخل ڪريو.",
        "summary-preview": "تت جي پيش نگاھ:",
        "subject-preview": "موضوع جي پيش نگاھ:",
        "blockedtitle": "واپرائيندڙ بندشيل آهي",
        "rev-deleted-event": "(لاگ تفصيل هٽايا ويا)",
        "rev-deleted-user-contribs": "[واپرائيندڙ-نانءُ يا آءِپِي پتو مِٽايو ويو - ڀاڱيدارين مان سنوار لڪائي وئي]",
        "rev-suppressed-no-diff": "توهان اهو تفاوت نٿا ڏسي سگھو، ڇاڪاڻ تہ ورجائن مان ڪو ھڪ <strong>ڊاھيو ويو آھي</strong>.",
-       "rev-delundel": "نمائش تبديل ڪريو",
+       "rev-delundel": "ظاھريت بدلايو",
        "rev-showdeleted": "ڏيکاريو",
        "revisiondelete": "ورجاءَ ڊاهيو/اڻ‌ڊاهيو",
        "revdelete-no-file": "ڄاڻايل فائيل وجود نٿو رکي.",
        "revdelete-failure": "ورجاءُ ظاھريت نہ جديدي سگھجي:\n$1",
        "logdelete-success": "لاگ ظاھريت مرتب ٿي.",
        "logdelete-failure": "لاگ ظاھريت مرتب نہ ٿي سگھجي:\n$1",
-       "revdel-restore": "نمائش تبديل ڪريو",
+       "revdel-restore": "ظاھريت بدلايو",
        "pagehist": "صفحي جي سوانح",
        "deletedhist": "ڊاٿل سوانح",
        "revdelete-otherreason": "ٻيا/اضافي ڪارڻ:",
        "prefs-editwatchlist-clear": "پنهنجي نظر ۾ فھرست ڊاهيو",
        "prefs-watchlist-days": "نظر ۾ فھرست ۾ ڏيکارڻ لاءِ ڏينهن:",
        "prefs-watchlist-days-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينھن}}",
-       "prefs-watchlist-edits": "نظر ۾ فھرست ز۾ ڏيکارڻ جي لاءِ تبديلين جو وڌ-۾-وڌ انگ:",
+       "prefs-watchlist-edits": "نظر ۾ فھرست ۾ ڏيکارڻ جي لاءِ تبديلين جو وڌ-۾-وڌ انگ:",
        "prefs-watchlist-edits-max": "وڌ ۾ وڌ تعداد: 1000",
        "prefs-watchlist-token": "نظر ۾ فھرست جو ٽوڪن:",
        "prefs-misc": "متفرق",
        "searchresultshead": "ڳولا",
        "stub-threshold-sample-link": "نمونو",
        "stub-threshold-disabled": "اڻ-فعايل",
-       "recentchangesdays": "تازين تبديلين ۾ ڏيکارڻ جي لاءِ ڏينهن:",
+       "recentchangesdays": "تازين تبديلين ۾ ڏيکارڻ جي لاءِ ڏينھن:",
        "recentchangesdays-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينهن}}",
-       "recentchangescount": "تازين تبديلين، صفحن جي سوانح، ۽ لاگس ۾ ڏيکارڻ لاءِ سنوارن جو خودڪار ڏنل انگ:",
+       "recentchangescount": "تازين تبديلين، صفحن جي سوانح، ۽ لاگس ۾ ڏيکارڻ لاءِ سنوارن جو ڏنل انگ:",
        "prefs-help-recentchangescount": "وڌ ۾ وڌ انگ: 1000",
        "savedprefs": "توھان جون ترجيحون سانڍجي چڪيون آھن.",
        "savedrights": "{{GENDER:$1|$1}} جا واپرائيندڙ گروھ سانڍجي چڪا آھن.",
        "yourrealname": "اصل نالو:",
        "yourlanguage": "ٻولي:",
        "yournick": "نئون دستخط",
-       "prefs-help-signature": "بحث ØµÙ\81Ø­Ù\8a ØªÙ\8a Ø±Ø§Ù\8aا Ú\8fÙ\8aÚ» Ù\88Ù\82ت Ù\87Ù\86 Ù\86شاÙ\86Ù\8aÙ\86 Ø°Ø±Ù\8aعÙ\8a \"<nowiki>~~~~</nowiki>\" Ø¯Ø³ØªØ®Ø· ÚªÙ\8aÙ\88Ø\8c Ø¬Ù\8aÚªÙ\8a Ù¾Ø§Ú»Ù\85رادÙ\88 ØªÙ\88Ù\87اÙ\86 Ø¬Ù\8a Ø¯Ø³ØªØ®Ø· Û½ Ù\88Ù\82ت Û¾ ØªØ¨Ø¯Ù\8aÙ\84 Ù¿ي ويندا.",
+       "prefs-help-signature": "بحث ØµÙ\81Ø­Ù\86 ØªÙ\8a Ø±Ø§Ù\8aا Ù\87Ù\86 \"<nowiki>~~~~</nowiki>\" Ø°Ø±Ù\8aعÙ\8a Ø¯Ø³ØªØ®Ø· ÚªØ±Ú» Ú¯Ú¾Ø±Ø¬Ù\86Ø\8c Ø¬Ù\8aÚªÙ\8a Ù¾Ø§Ú»Ù\85رادÙ\88 ØªÙ\88Ù\87اÙ\86 Ø¬Ù\8a Ø¯Ø³ØªØ®Ø· Û½ Ù\88Ù\82ت-ٺپÙ\8a Û¾ Ù\85ٽجي ويندا.",
        "badsiglength": "اهو درتخط هيڪاندو ڊگھو آهي.\nاها وڌ ۾ وڌ $1 {{PLURAL:$1|اکر|اکرن}} تي ٻڌل هجڻ گھرجي.",
        "yourgender": "توھان ڪيئن بيان ٿيڻ چاھيندا؟",
        "gender-unknown": "توهان جو ذڪر ڪندي، جيترو ٿي سگھيو، منطقگري بي جنس لفظن جو استعمال ڪندي.",
        "userrights-reason": "سبب:",
        "userrights-no-interwiki": "توهان کي ٻين وڪين تي واپرائيندڙ حق سنوارڻ جي اجازت ناھي.",
        "userrights-nodatabase": "اعداخانو $1 يا تہ وجود نٿو رکي يا تہ اهو مقامي اعدادخانو نہ آهي.",
-       "userrights-changeable-col": "گروپَ جيڪي توهان تبديل ڪري سگھو ٿا",
-       "userrights-unchangeable-col": "گروپَ جيڪي توهان تبديل نٿا ڪري سگھو",
+       "userrights-changeable-col": "گروھَ جيڪي توهان بدلائي سگھو ٿا",
+       "userrights-unchangeable-col": "گروھَ جيڪي توهان بدلائي نٿا سگھو",
        "userrights-irreversible-marker": "$1*",
        "userrights-no-shorten-expiry-marker": "$1#",
        "userrights-expiry-current": "مدو پورو $1",
        "userrights-expiry-options": "1 ڏينھن:1 ڏينھن،1 ھفتو:1 ھفتا،1 مھينو:1 مھينو،3 مھينا:3 مھينا،6 مھينا:6 مھينا،1 سال:1 سال",
        "group": "گروھ:",
        "group-user": "واپرائيندڙَ",
-       "group-autoconfirmed": "خودبخود پڪ ڪيل واپرائيندڙَ",
+       "group-autoconfirmed": "پاڻمرادو-پڪ-ڪيل واپرائيندڙَ",
        "group-bot": "بوٽس",
        "group-sysop": "منتظم",
        "group-interface-admin": "منتظم براءِ حليو",
        "action-editinterface": "واپرائيندڙ انٽرفيس سنواريو",
        "action-editusercss": "واپرائيندڙن جا سي.ايس.ايس فائيل سنواريو",
        "action-unblockself": "ڪنھن جي بندش ختم ڪريو",
-       "nchanges": "$1 {{PLURAL:$1|تبدÙ\8aÙ\84Ù\8a|تبدÙ\8aÙ\84Ù\8aÙ\88Ù\86}}",
+       "nchanges": "$1 {{PLURAL:$1|بدÙ\84اءÙ\8f|بدÙ\84اءÙ\8e}}",
        "ntimes": "$1ڀيرا",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|آخري ڦيري کان}}",
        "enhancedrc-history": "سوانح",
        "rcfilters-activefilters-show-tooltip": "سرگرم ڇاڻين جي ايراضي ڏيکاريو",
        "rcfilters-advancedfilters": "متقدم ڇاڻيون",
        "rcfilters-limit-title": "ڏيکارڻ لاءِ نتيجا",
-       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|تبدÙ\8aÙ\84Ù\8a|$1 ØªØ¨Ø¯Ù\8aÙ\84Ù\8aÙ\88Ù\86}}، $2",
+       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|بدÙ\84اءÙ\8f|$1 Ø¨Ø¯Ù\84اءÙ\8e}}، $2",
        "rcfilters-date-popup-title": "ڳولڻ لاءِ وقت جو دورانيو",
        "rcfilters-days-title": "ھاڻوڪا ڏينھن",
        "rcfilters-hours-title": "ھاڻوڪا ڪلاڪَ",
        "rcfilters-filter-logactions-label": "لاگڊ عمل",
        "rcfilters-filtergroup-lastrevision": "تازا-ترين ورجاءَ",
        "rcfilters-filter-lastrevision-label": "تازو-ترين ورجاءُ",
-       "rcfilters-filter-lastrevision-description": "ÚªÙ\86Ú¾Ù\86 ØµÙ\81Ø­Ù\8a Û¾ Ø±Ú³Ù\88 ØªØ§Ø²Ù\8a-ترÙ\8aÙ\86 ØªØ¨Ø¯Ù\8aÙ\84Ù\8a.",
+       "rcfilters-filter-lastrevision-description": "ÚªÙ\86Ú¾Ù\86 ØµÙ\81Ø­Ù\8a Û¾ Ø±Ú³Ù\88 ØªØ§Ø²Ù\88-ترÙ\8aÙ\86 Ø¨Ø¯Ù\84اءÙ\8f.",
        "rcfilters-filter-previousrevision-label": "تازو-ترين ورجاءُ نہ",
        "rcfilters-filter-previousrevision-description": "سڀ تبديليون جيڪي \"تازو-ترين ورجاءُ\" ناھن.",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:نہ</strong> $1",
        "rcfilters-watchlist-markseen-button": "سڀ تبديلين کي ڏٺل طور نشان لڳايو",
        "rcfilters-watchlist-edit-watchlist-button": "پنھنجي نظر ۾ صفحن جي فھرست سنواريو",
        "rcfilters-alldiscussions-label": "سڀ گفتگوئون",
-       "rcnotefrom": "Ù\87Ù\8aÙº {{PLURAL:$5|تبدÙ\8aÙ\84Ù\8a Ø¢Ù\87Ù\8a|تبدÙ\8aÙ\84Ù\8aÙ\88Ù\86 Ø¢Ù\87Ù\86}} Ú©Ø§Ù\86 <strong>$3, $4</strong> (تائين <strong>$1</strong> ) ڏيکاريل آهن.",
+       "rcnotefrom": "Ù\87Ù\8aÙº {{PLURAL:$5|بدÙ\84اءÙ\8f Ø¢Ù\87Ù\8a|بدÙ\84اءÙ\8e Ø¢Ù\87Ù\86}} Ú©Ø§Ù\86 <strong>$3Ø\8c $4</strong> (تائين <strong>$1</strong> ) ڏيکاريل آهن.",
        "rclistfromreset": "تاريخ چونڊڻ ٻيھر مرتب ڪريو",
        "rclistfrom": "$2، $3 کان شروع ٿيندڙ نيون تبديليون ڏيکاريو",
        "rcshowhideminor": "$1 معمولي سنوارون",
        "uploaded-hostile-svg": "چاڙھيل ايس.وي.جي فائيل جو غير محفوظ سي.ايس.ايس اسٽائيل ايلمينٽ ۾ مليو.",
        "uploaded-event-handler-on-svg": "ايس وي جي فائيل ۾ ايوينٽ هينڊلر خصوصيتون <code>$1=\"$2\"</code> مقرر ڪرڻ جي اجازت نہ آهي.",
        "uploaded-href-unsafe-target-svg": "href جو غير محفوظ ڊيٽا: يوآرآءِ نشانو مليو آهي <code>&lt;$1 $2=\"$3\"&gt;</code> چاڙھيل اَيسوِيجِي فائيل ۾",
-       "uploaded-animate-svg": "”اينيميٽ“ ٽيگ ڳوليو جيڪو ٿي سگھي ٿو href کي تبديل ڪري رهي هجي، چاڙھيل ايس.وي.جي فائيل ۾ \"form\" وصف استعمال ڪندي <code>&lt;$1 $2=\"$3\"&gt;</code>",
+       "uploaded-animate-svg": "”اينيميٽ“ ٽيگ لڌو جيڪو ٿي سگھي ٿو href کي بدلائي رهيو هجي، چاڙھيل ايسوِيجِي فائيل ۾ \"form\" وصف استعمال ڪندي <code>&lt;$1 $2=\"$3\"&gt;</code>",
        "uploaded-setting-event-handler-svg": "موقعو-سنڀاليندڙ جا انتساب ترتيبڻ بندشيل آهن، <code>&lt;$1 $2=\"$3\"&gt;</code> چاڙھيل ايس.وي.جي فائيل ۾ مليو",
        "uploaded-setting-href-svg": "\"set\"  ٽيگ کي \"href\" وصف استعمال ڪندي بنيادي عنصر کي بندشيو ويو آھي.",
        "uploaded-wrong-setting-svg": "\"set\" ٽيگ کي استعمال ڪندي رموٽ/ڊيٽا/اسڪرپٽ ٽارگيٽ کي ڪنھڻ وصف سان جوڙڻ کي بلاڪ ڪيو ويو آهي. \n<code>&lt;set to=\"$1\"&gt;</code> چاڙھيل ايس.وي.جي فائيل ۾ مليو آهي.",
        "randomincategory-submit": "هلو",
        "randomredirect": "بلاترتيب چورڻو",
        "statistics": "انگ-اکر",
-       "statistics-header-pages": "صفحي انگ اکر",
+       "statistics-header-pages": "صفحي انگ-اکر",
        "statistics-header-edits": "سنوار جا انگ-اکر",
-       "statistics-header-users": "واپرائيندڙن جا انگ اکر",
+       "statistics-header-users": "واپرائيندڙن جا انگ-اکر",
        "statistics-header-hooks": "ٻيا انگ-اکر",
        "statistics-articles": "موادي صفحا",
        "statistics-pages": "صفحا",
        "protectedpages-unknown-performer": "اڻڄاتل واپرائيندڙ",
        "protectedtitles": "تحفظيل عنوان",
        "protectedtitles-submit": "عنوان ڏيکاريو",
-       "listusers": "واپرائيندڙن جي فهرست",
+       "listusers": "واپرائيندڙن جي فھرست",
        "listusers-editsonly": "رڳو سنوارن وارا واپرائيندڙ ڏيکاريو",
        "listusers-temporarygroupsonly": "صرف عارضي واپرائيندڙ گروھن ۾ واپرائيندڙ ڏيکاريو",
        "listusers-creationsort": "سرجڻ جي تاريخ سان مرتب ڪريو",
        "unwatching": "نظر مان ڪڍندي...",
        "enotif_reset": "سڀ گھميل صفحن تي نشان لڳايو",
        "enotif_impersonal_salutation": "{{SITENAME}} واپرائيندڙ",
-       "enotif_lastdiff": "Ù\87Ù\8a ØªØ¨Ø¯Ù\8aÙ\84Ù\8a ڏسڻ لاءِ، $1 ڏسو",
+       "enotif_lastdiff": "Ù\87Ù\8a Ø¨Ø¯Ù\84اءÙ\8f ڏسڻ لاءِ، $1 ڏسو",
        "enotif_anon_editor": "گمنام واپرائيندڙ $1",
        "enotif_minoredit": "هيءَ ننڍڙي سنوار آهي",
        "created": "ٺهي چڪو",
-       "changed": "تبدÙ\8aÙ\84 Ù¿ي ويو",
+       "changed": "بدÙ\84جي ويو",
        "deletepage": "صفحو ڊاهيو",
        "confirm": "پڪ ڪريو",
        "delete-confirm": "\"$1\" ڊهي چڪو",
        "confirmdeletetext": "توهان هڪ صفحي کي ان جي سموري سوانح سميت ڊاهڻ وارا آهيو. مهرباني ڪري پڪ ڪندا ته توهان اهو ئي ڪرڻ گھرو ٿا، ۽ اهو ته توهان ان جي نتيجن کان واقف آهيو، ۽ اهو پڻ ته توهان اهو ڪم [[{{MediaWiki:Policy-url}}|پاليسي]]ءَ مطابق ڪري رهيا آهيو.",
        "actioncomplete": "ڪم پُورو",
        "actionfailed": "عمل ناڪام",
-       "deletedtext": "\"$1\" ڊهي چڪو آهي.\nتازو ڊاٺل صفحن جي فهرست لاءِ $2 ڏسندا.",
+       "deletedtext": "\"$1\" ڊھي چڪو آھي.\nتازين ڊاھن صفحن جي رڪارڊ لاءِ $2 ڏسو.",
        "dellogpage": "ڊاھ لاگ",
        "deletionlog": "ڊاٺ لاگ",
        "deletecomment": "سبب:",
        "rollbacklink": "واپس ورايو",
        "rollbacklinkcount": "$1 {{PLURAL:$1|سنوار|سنوارون}} واپس-ورايو",
        "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|بحث]]) پاران سنوارون واپس [[User:$1|$1]] جي آخري مسودي ڏانھن ڪيون ويون",
-       "changecontentmodel-title-label": "صفحي جو عنوان",
+       "changecontentmodel-title-label": "صفحي جو عنوان:",
        "changecontentmodel-reason-label": "سبب:",
        "changecontentmodel-submit": "بدلايو",
        "logentry-contentmodel-change-revertlink": "واپس ورايو",
        "logentry-contentmodel-change-revert": "واپس ورايو",
        "protectlogpage": "تحفظ لاگ",
        "protectedarticle": "محفوظ ٿيل \"[[$1]]\"",
-       "modifiedarticleprotection": "\"[[$1]]\" جي تحفظ جي سطح تبديل ڪئي",
+       "modifiedarticleprotection": "\"[[$1]]\" لاءِ تحفظي سطح بدلائي",
        "unprotectedarticle": "\"[[$1]]\" تان تحفظ ھٽايو ويو",
        "movedarticleprotection": "\"[[$2]]\" جو حفاظت درجو \"[[$1]]\" جي طرف منتقل ڪيو",
        "unprotectedarticle-comment": "\"[[$1]]\" تان {{GENDER:$2|تحفظ ھٽايو}}",
        "protect_expiry_invalid": "انجامي مدو ناقابلڪار آهي.",
        "protect_expiry_old": "انجامي مدو ماضيءَ ۾ آهي.",
        "protect-text": "توهان '''$1''' صفحي جي تحفظاتي سطح ڏسي ۽ بدلائي سگھو ٿا.",
-       "protect-locked-access": "تÙ\88Ù\87اÙ\86 Ø¬Ù\88 Ú©Ø§ØªÙ\88 ØµÙ\81Ø­Ù\86 Ø¬Ù\8a ØªØ­Ù\81ظاتÙ\8a Ø³Ø·Ø­ ØªØ¨Ø¯Ù\8aÙ\84Ù\8a ÚªØ±Ú» Ø¬Ø§ Ø§Ø®ØªÙ\8aار Ù\86Ù\87 Ù¿Ù\88 Ø±Ú©Ù\8a. Ù\87Ù\8aÙº ØµÙ\81Ø­Ù\8a Ø¬Ù\88Ù\86 Ù\88Ù\82Ù\88عات (سÙ\8aٽڱس) Ù¾Ù\8aØ´ ÚªØ¬Ù\86 Ù¿Ù\8aÙ\88Ù\86 '''$1''':",
+       "protect-locked-access": "تÙ\88Ù\87اÙ\86 Ø¬Ù\8a Ú©Ø§ØªÙ\8a Ú©Ù\8a ØµÙ\81Ø­Ù\86 Ø¬Ù\8a ØªØ­Ù\81ظ Ø³Ø·Ø­ Ø¨Ø¯Ù\84ائڻ Ø¬Ù\8a Ø§Ø¬Ø§Ø²Øª Ù\86Û\81 Ø¢Ú¾Ù\8a.\nھتÙ\8a ØµÙ\81Ø­Ù\8a Ù\84اءÙ\90 Ú¾Ø§Ú»Ù\88ÚªÙ\8aÙ\88Ù\86 ØªØ±ØªÙ\8aبÙ\88Ù\86 Ø¢Ú¾Ù\86 <strong>$1</strong>:",
        "protect-cascadeon": "هيءُ صفحو في الوقت تحفظيل آهي، ڇاڪاڻ ته اهو هيٺين {{PLURAL:$1|صفحي|صفحن}} جو حصو آهي، جنهن تي تحفظ در تحفظ لاڳو ٿيل آهي.\nChanges to this page's protection level will not affect the cascading protection.",
        "protect-default": "سڀ واپرائيندڙن کي اجازت ڏيو",
        "protect-fallback": "\"$1\" جي اجازت وارن واپرائيندڙن کي اجازت ڏيو",
        "mycontris": "ڀاڱيداريون",
        "anoncontribs": "ڀاڱيداريون",
        "contribsub2": "{{GENDER:$3|$1}} ($2) لاءِ",
+       "contributions-subtitle": "{{GENDER:$3|$1}} لاءِ",
        "contributions-userdoesnotexist": "واپرائيندڙ کاتو \"$1\" درج ٿيل نہ آهي.",
        "nocontribs": "هن معيار سان ملندڙ ڪي بہ تبديليون نہ لڌيون ويون.",
        "uctop": "هاڻوڪو",
        "month": "مھيني کان (۽ اڳوڻيون):",
        "year": "سال کان (۽ اڳوڻيون):",
+       "date": "تاريخ کان (۽ اڳ):",
        "sp-contributions-blocklog": "بندش لاگ",
        "sp-contributions-suppresslog": "{{GENDER:$1|واپرائيندڙ}} جو دٻايل ڀاڱيداريون",
        "sp-contributions-deleted": "{{GENDER:$1|واپرائيندڙ}} جون ڊاٿل ڀاڱيداريون",
        "unlockbtn": "اعدادخاني کي کوليو",
        "move-page": "$1 چوريو",
        "move-page-legend": "صفحو چوريو",
-       "movepagetext": "هيٺيون فارم استعمال ڪندي ڪنھن صفحي کي نئون عنوان ڏئي سگھجي ٿو، جنھن سان سمورو صفحو نئين عنوان ڏانھن هليو ويندو. \nاڳوڻو عنوان نئين عنوان ڏانھن چورڻو بڻجي ويندو. \nتوهان  چورڻن کي سنواري سگھو ٿا جيڪي اصل عنوان ڏانهن خودبخود اشارو ڪن ٿا.\nانهي ڳالھ جي پڪ ڪري وٺو تہ [[Special:BrokenRedirects|ٽٽل چورڻا]] يا [[Special:DoubleRedirects|ٻٽا چورڻا]] نہ هجن.\nان ڳالھ جي پڪ ڪرڻ ذميواري توهان تي آهي تہ ڳنڍڻا اتي ئي وٺي وڃن ٿا جتي انھن کي وٺي وڃڻ گھرجي.\n\nياد رکندا تہ جيڪڏهن نئين عنوان سان اڳي ئي ڪو مضمون موجود آهي ته پوءِ صفحو '''نہ''' چوريو ويندو، سواءِ ان جي تہ موجوده صفحو محظ خالي آهي يا ڪا بہ سوانح نہ رکندڙ ڪو چورڻو آهي.\n\n<strong>نوٽ!</strong>\nاها هڪ مقبول صفحي لاءِ ڪا غير متوقع ۽ انتھائي اڻوڻندڙ تبديلي ثابت ٿي سگھي ٿي؛ براءِ مھرباني اڳتي وڌڻ کان اڳ پڪ ڪندا تہ توهان اها تبديلي آڻڻ جي نتيجن کان چڱيءَ ريت واقف آهيو.",
-       "movepagetext-noredirectfixer": "هيٺيون فارم استعمال ڪندي ڪنھن صفحي کي نئون عنوان ڏئي سگھجي ٿو، جنھن سان سمورو صفحو نئين عنوان ڏانھن هليو ويندو. \nاڳوڻو عنوان نئين عنوان ڏانھن چورڻو بڻجي ويندو. \nتوهان  چورڻن کي سنواري سگھو ٿا جيڪي اصل عنوان ڏانھن خودبخود اشارو ڪن ٿا.\nانهي ڳالھ جي پڪ ڪري وٺو تہ [[Special:BrokenRedirects|ٽٽل چورڻا]] يا [[Special:DoubleRedirects|ٻٽا چورڻا]] نہ هجن.\nان ڳالھ جي پڪ ڪرڻ ذميواري توهان تي آهي تہ ڳنڍڻا اتي ئي وٺي وڃن ٿا جتي انھن کي وٺي وڃڻ گھرجي.\n\nياد رکندا تہ جيڪڏهن نئين عنوان سان اڳي ئي ڪو مضمون موجود آهي ته پوءِ صفحو '''نہ''' چوريو ويندو، سوا ان جي تہ موجوده صفحو محظ خالي آهي يا ڪا بہ سوانح نہ رکندڙ ڪو چورڻو آهي.\n\n<strong>نوٽ!</strong>\nاها هڪ مقبول صفحي لاءِ ڪا غير متوقع ۽ انتھائي اڻوڻندڙ تبديلي ثابت ٿي سگھي ٿي؛ مھرباني ڪري اڳتي وڌڻ کان اڳ پڪ ڪندا تہ توهان اها تبديلي آڻڻ جي نتيجن کان چڱيءَ ريت واقف آهيو.",
+       "movepagetext": "هيٺيون فارم استعمال ڪندي ڪنھن صفحي کي نئون عنوان ڏئي سگھجي ٿو، جنھن سان سمورو صفحو نئين عنوان ڏانھن هليو ويندو. \nاڳوڻو عنوان نئين عنوان ڏانھن چورڻو بڻجي ويندو. \nتوهان  چورڻن کي سنواري سگھو ٿا جيڪي اصل عنوان ڏانهن پاڻمرادو اشارو ڪن ٿا.\nانهي ڳالھ جي پڪ ڪري وٺو تہ [[Special:BrokenRedirects|ٽٽل چورڻا]] يا [[Special:DoubleRedirects|ٻٽا چورڻا]] نہ هجن.\nان ڳالھ جي پڪ ڪرڻ ذميواري توهان تي آهي تہ ڳنڍڻا اتي ئي وٺي وڃن ٿا جتي انھن کي وٺي وڃڻ گھرجي.\n\nياد رکندا تہ جيڪڏهن نئين عنوان سان اڳي ئي ڪو مضمون موجود آهي ته پوءِ صفحو '''نہ''' چوريو ويندو، سواءِ ان جي تہ موجوده صفحو محظ خالي آهي يا ڪا بہ سوانح نہ رکندڙ ڪو چورڻو آهي.\n\n<strong>نوٽ!</strong>\nاھو ھڪ مقبول صفحي لاءِ ڪو غير متوقع ۽ انتھائي اڻوڻندڙ بدلاءُ ثابت ٿي سگھي ٿو؛ مھرباني ڪري اڳتي وڌڻ کان اڳ پڪ ڪندا تہ توهان اھو بدلاءُ آڻڻ جي نتيجن کان چڱيءَ ريت واقف آهيو.",
+       "movepagetext-noredirectfixer": "هيٺيون فارم استعمال ڪندي ڪنھن صفحي کي نئون عنوان ڏئي سگھجي ٿو، جنھن سان سمورو صفحو نئين عنوان ڏانھن هليو ويندو. \nاڳوڻو عنوان نئين عنوان ڏانھن چورڻو بڻجي ويندو. \nتوهان  چورڻن کي سنواري سگھو ٿا جيڪي اصل عنوان ڏانھن پاڻمرادو اشارو ڪن ٿا.\nانهي ڳالھ جي پڪ ڪري وٺو تہ [[Special:BrokenRedirects|ٽٽل چورڻا]] يا [[Special:DoubleRedirects|ٻٽا چورڻا]] نہ هجن.\nان ڳالھ جي پڪ ڪرڻ ذميواري توهان تي آهي تہ ڳنڍڻا اتي ئي وٺي وڃن ٿا جتي انھن کي وٺي وڃڻ گھرجي.\n\nياد رکندا تہ جيڪڏهن نئين عنوان سان اڳي ئي ڪو مضمون موجود آهي ته پوءِ صفحو '''نہ''' چوريو ويندو، سوا ان جي تہ ھاڻوڪو صفحو محظ خالي آهي يا ڪا بہ سوانح نہ رکندڙ ڪو چورڻو آهي.\n\n<strong>نوٽ!</strong>\nاھو هڪ مقبول صفحي لاءِ ڪو غير متوقع ۽ انتھائي اڻوڻندڙ بدلاءُ ثابت ٿي سگھي ٿو؛ مھرباني ڪري اڳتي وڌڻ کان اڳ پڪ ڪندا تہ توهان اهو بدلاءُ آڻڻ جي نتيجن کان چڱيءَ ريت واقف آهيو.",
        "movepagetalktext": "جيڪڏهن توهان هن خاني کي نشان لڳائيندئو، واسطيدار مباحثي صفحو پاڻ ئي چوريو ويندو ماسواءِ اتي ڪو اڳ ئي ڪو غيرخالي مباحثي صفحو موجود هجي.\n\nان صورت ۾، جيڪڏهن توهان چاهيو ته صفحي کي پاڻ چوري يا ضم ڪري سگھو ٿا.",
        "movecategorypage-warning": "<strong>چتاءُ:</strong> اوهان زمري واري صفحي کي چورڻ وڃي رهيا آهيو. ياد رکو رڳو صفحو چرندو، پراڻي زمري ۾ ڪن بہ صفحن جي نئين صفحي ۾ ٻيھر-زمراڪاري <em>نہ</em> ڪئي ويندي.",
        "movenotallowed": "توهان کي صفحا چورڻ جي اجازت حاصل ڪانهي.",
        "import-upload-filename": "فائيل نانءُ:",
        "import-comment": "تاثر:",
        "importstart": "صفحا درآمد ٿين پيا...",
+       "import-revision-count": "$1 {{PLURAL:$1|ورجاءُ|ورجاءَ}}",
        "importlogpage": "درآمد لاگ",
        "tooltip-pt-userpage": "{{GENDER:|توھانجو}} صفحو",
        "tooltip-pt-mytalk": "{{GENDER:|توھانجو}} بحث صفحو",
        "tooltip-minoredit": "ھن کي هڪ معمولي سنوار طور نشان لڳايو",
        "tooltip-save": "پنھنجون تبديليون سانڍيو",
        "tooltip-publish": "پنهنجيون تبديليون ڇاپيو",
-       "tooltip-preview": "پنھنجي تبديلين تي نگاھ وجھو. براءِ مھرباني اھو سانڍڻ کان اڳ ڪندا.",
+       "tooltip-preview": "پنھنجي تبديلين تي نگاھ وجھو. مھرباني ڪري اھو سانڍڻ کان اڳ ڪندا.",
        "tooltip-diff": "لکت ۾ ڪيل پنھنجون تبديليون ڏسو",
        "tooltip-compareselectedversions": "هن صفحي جن وچ ۾ چونڊيل ورجائن وچ ۾ تفاوت ڏسو",
        "tooltip-watch": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪريو",
        "ilsubmit": "ڳوليو",
        "bydate": "تاريخوار",
        "days-abbrev": "$1 ڏ",
+       "seconds": "{{PLURAL:$1|$1 سيڪنڊ|$1 سيڪنڊَ}}",
+       "minutes": "{{PLURAL:$1|$1 منٽ|$1 منٽَ}}",
        "hours": "{{PLURAL:$1|$1 ڪلاڪ|$1 ڪلاڪَ}}",
        "days": "{{PLURAL:$1|$1 ڏينهن|$1 ڏينهَن}}",
        "weeks": "{{PLURAL:$1|$1 هفتو|$1 هفتا}}",
        "confirm-watch-button": "ٺيڪ",
        "confirm-watch-top": "هيءُ صفحو پنهنجي نظر ۾ فھرست ۾ شامل ڪندا؟",
        "confirm-unwatch-button": "ٺيڪ",
-       "confirm-unwatch-top": "هيءُ صفحو پنهنجي نظر ۾ فهرست مان هٽائيندا؟",
+       "confirm-unwatch-top": "هيءُ صفحو پنھنجي نظر ۾ فھرست مان هٽائيندا؟",
        "confirm-rollback-top": "ھن صفحي ۾ ڪيل سنوارون واپس ورايون؟",
        "semicolon-separator": "؛&#32;",
        "comma-separator": "،&#32;",
        "watchlistedit-clear-titles": "عنوانَ:",
        "watchlisttools-clear": "نظر ۾ فھرست صاف ڪريو",
        "watchlisttools-view": "لاڳاپيل تبديليون ڏسو",
-       "watchlisttools-edit": "نظر ۾ فهرست ڏسو ۽ سنواريو",
+       "watchlisttools-edit": "نظر ۾ فھرست ڏسو ۽ سنواريو",
        "watchlisttools-raw": "ڪچي نظر ۾ فھرست سنواريو",
        "hijri-calendar-m1": "محرم",
        "hijri-calendar-m2": "صفر",
        "specialpages-group-media": "ميڊيا رپورٽ ۽ چاڙهيل",
        "specialpages-group-users": "واپرائيندڙَ ۽ حق",
        "specialpages-group-highuse": "وڌيڪ استعمال وارا صفحا",
-       "specialpages-group-pages": "صفحن جي فهرست",
+       "specialpages-group-pages": "صفحن جي فھرست",
        "specialpages-group-pagetools": "صفحن جا اوزار",
        "blankpage": "خالي صفحو",
        "intentionallyblankpage": "هيءُ صفحو ڄاڻي خالي ڇڏيو ويو آهي.",
        "tags-delete": "ڊاهيو",
        "tags-activate": "فعال بڻايو",
        "tags-deactivate": "غير فعال بڻايو",
-       "tags-hitcount": "$1 {{PLURAL:$1|تبدÙ\8aÙ\84Ù\8a|تبدÙ\8aÙ\84Ù\8aÙ\88Ù\86}}",
+       "tags-hitcount": "$1 {{PLURAL:$1|بدÙ\84اءÙ\8f|بدÙ\84اءÙ\8e}}",
        "tags-create-tag-name": "ٽيگ نانءُ:",
        "tags-create-reason": "سبب:",
        "tags-create-submit": "سرجيو",
        "htmlform-yes": "ها",
        "htmlform-cloner-create": "ٻيا بہ شامل ڪريو",
        "htmlform-cloner-delete": "هٽايو",
+       "htmlform-date-placeholder": "سسسس-مم-ڏڏ",
        "htmlform-title-not-exists": "$1 وجود نٿو رکي.",
        "logentry-delete-delete": "$1 {{GENDER:$2|ڊاٿو}} صفحو $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|بحاليو}} صفحو $3 ($4)",
-       "logentry-delete-revision": "$1 $3: $4 ØµÙ\81Ø­Ù\8a ØªÙ\8a {{PLURAL:$5|Ú¾Úª Ù\88رجاءÙ\8e|$5 Ù\88رجائÙ\86}} Ø¬Ù\8a Ø¸Ø§Ú¾Ø±Ù\8aت {{GENDER:$2|تبدÙ\8aÙ\84 Úªئي}}",
+       "logentry-delete-revision": "$1 $3: $4 ØµÙ\81Ø­Ù\8a ØªÙ\8a {{PLURAL:$5|Ú¾Úª Ù\88رجاءÙ\8e|$5 Ù\88رجائÙ\86}} Ø¬Ù\8a Ø¸Ø§Ú¾Ø±Ù\8aت {{GENDER:$2|بدÙ\84ائي}}",
        "revdelete-content-hid": "مواد لڪيل",
        "revdelete-uname-hid": "واپرائيندڙ-نانءُ لڪل",
        "revdelete-unrestricted": "منتظمن تان پابنديون ھٽايون ويون",
        "searchsuggest-search": "ڳوليو {{SITENAME}}",
        "api-error-unknown-warning": "اڻڄاتل چتاءُ: \"$1\".",
        "api-error-unknownerror": "اڻڄاتل چُڪَ: \"$1\".",
+       "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|هفتو|هفتا}}",
        "expand_templates_output": "نتيجو",
        "expand_templates_ok": "ٺيڪ",
        "expand_templates_remove_comments": "تاثرات مِٽايو",
index cd4e8dd..10882ad 100644 (file)
@@ -71,7 +71,7 @@
        "tog-shownumberswatching": "Zobraziť počet používateľov sledujúcich stránku",
        "tog-oldsig": "Váš súčasný podpis:",
        "tog-fancysig": "Považovať podpisy za wikitext (bez automatických odkazov)",
-       "tog-uselivepreview": "Používať živý náhľad",
+       "tog-uselivepreview": "Zobrazovať náhľady bez obnovenia stránky",
        "tog-forceeditsummary": "Upozoriť ma, keď nevyplním zhrnutie úprav",
        "tog-watchlisthideown": "Skryť moje úpravy zo zoznamu sledovaných",
        "tog-watchlisthidebots": "Skryť úpravy botov zo zoznamu sledovaných",
        "createacct-another-submit": "Vytvoriť účet",
        "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-heading": "{{GRAMMAR:akuzatív|{{SITENAME}}}} tvoria ľudia ako 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}}",
        "rcfilters-empty-filter": "Žiadne aktívne filtre. Všetky príspevky sú zobrazené.",
        "rcfilters-filterlist-title": "Filtre",
        "rcfilters-filterlist-whatsthis": "Ako to funguje?",
-       "rcfilters-filterlist-feedbacklink": "Povedzte nám, čo si myslíte o týchto (nových) filtroch",
+       "rcfilters-filterlist-feedbacklink": "Povedzte nám, čo si myslíte o týchto filtroch",
        "rcfilters-highlightbutton-title": "Zvýrazniť výsledky",
        "rcfilters-highlightmenu-title": "Vybrať farbu",
        "rcfilters-highlightmenu-help": "Vyberte farbu pre zvýraznenie tejto vlastnosti",
index 5fe33ce..dcc686b 100644 (file)
        "undo-norev": "Redigeringen kan inte göras ogjord eftersom den inte finns eller har raderats.",
        "undo-nochange": "Det verkar som att redigeringen redan har blivit ogjord.",
        "undo-summary": "Gör version $1 av [[Special:Contributions/$2|$2]] ([[User talk:$2|diskussion]]) ogjord",
+       "undo-summary-anon": "Ångra sidversionen $1 av [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Gör version $1 av en dold användare ogjord",
        "cantcreateaccount-text": "[[User:$3|$3]] har blockerat den här IP-adressen ('''$1''') från att registrera konton.\n\nAnledningen till blockeringen var \"$2\".",
        "cantcreateaccount-range-text": "IP-adresserna i intervallet <strong>$1</strong>, som inkluderar din IP-adress (<strong>$4</strong>), har blockerats från att skapa konton av [[User:$3|$3]].\n\nAnledningen enligt $3 var <em>$2</em>",
        "alreadyrolled": "Det gick inte att rulla tillbaka den senaste redigeringen av [[User:$2|$2]] ([[User talk:$2|diskussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) på sidan [[:$1|$1]]. Någon annan har redan rullat tillbaka eller redigerat sidan.\n\nSidan ändrades senast av [[User:$3|$3]] ([[User talk:$3|diskussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]).",
        "editcomment": "Redigeringskommentaren var: <em>$1</em>.",
        "revertpage": "Återställde redigeringar av  [[Special:Contributions/$2|$2]] ([[User talk:$2|användardiskussion]]) till senaste versionen av [[User:$1|$1]]",
+       "revertpage-anon": "Ångrade redigeringar av [[Special:Contributions/$2|$2]] till senaste sidversionen av [[User:$1|$1]]",
        "revertpage-nouser": "Återställde redigeringar av en dold användare till den senaste versionen av {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Återställde ändringar av {{GENDER:$3|$1}};\nändrade tillbaka till senaste versionen av {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Sessionsfel",
index d80d061..8446ebd 100644 (file)
@@ -29,7 +29,7 @@
        "tog-hideminor": "Schŏw drobne pōmiany we niydŏwno pōmiynianych",
        "tog-hidepatrolled": "Schŏw przichwŏlōne pōmiany we niydŏwno pōmiynianych",
        "tog-newpageshidepatrolled": "Schŏw przichwŏlōne zajty na wykŏzie nowych zajtōw",
-       "tog-extendwatchlist": "Pokŏż na mojij pozōrliście wszyjske, a niy jyno niydŏwne pōmiany",
+       "tog-extendwatchlist": "We ôbserwowanych pokazuj wszyjske zmiany, a niy ino ôstatnie",
        "tog-usenewrc": "Potajluj pōmiany podug zajtōw we niydŏwnych pōmianach i pozōrliście",
        "tog-numberheadings": "Automatycznŏ nōmeracyjŏ titlōw",
        "tog-editondblclick": "Edycyjõ napoczynajōm dwa klikniyncia (JavaScript)",
@@ -46,8 +46,8 @@
        "tog-enotifminoredits": "Wyślij e-brifa tyż, kej by szło uo drobne pomjyńańa",
        "tog-enotifrevealaddr": "Ńy chow mojigo e-brifa we powjadomjyńach",
        "tog-shownumberswatching": "Pokoż, wjela sprowjorzy dowo pozůr",
-       "tog-oldsig": "Teroźni wyglůnd Twojygo szrajbowańo",
-       "tog-fancysig": "Szrajbńij ze kodůma wiki (bez autůmatycznygo linka)",
+       "tog-oldsig": "Twōj terŏźny podpis:",
+       "tog-fancysig": "Traktuj podpis jako wikitext (bez autōmatycznego linka)",
        "tog-uselivepreview": "Używej dynamiczne uobźyrańy (JavaScript) (ekszperymentalny)",
        "tog-forceeditsummary": "Pedź, kejbych ńic ńy naszkryfloł we uopiśe pomjyńań",
        "tog-watchlisthideown": "Schow moje pomjyńańa we artiklach, na kere dowom pozůr",
@@ -57,7 +57,7 @@
        "tog-watchlisthideanons": "Schow sprowjyńa anůńimowych sprowjoczy na liśće artikli, na kere dowom pozůr",
        "tog-watchlisthidepatrolled": "Schowej sprowdzůne sprowjyńa na pozorliśće",
        "tog-ccmeonemails": "Przesyłej mi kopje e-brifůw co żech je posłoł inkszym sprowjaczom",
-       "tog-diffonly": "Ńy pokozuj treśći zajtůw půniżyj porůwnańo pomjyńań",
+       "tog-diffonly": "Niy pokazuj treści strōn pod porōwaniami zmian",
        "tog-showhiddencats": "Pokoż schowane kategoryje",
        "tog-norollbackdiff": "Uomiń pokozywańy pomjyńań po użyću funkcyje „cofej”",
        "tog-useeditwarning": "Uostrzegej mje, kej uopuszczom zajta edycyji bez spamjyntańo půmjań",
        "morenotlisted": "Ńy je to kůmplytno lista",
        "mypage": "Zajta",
        "mytalk": "Dyskusyjŏ",
-       "anontalk": "Godka tygo IP",
+       "anontalk": "Dyskusyjŏ",
        "navigation": "Nawigacyjŏ",
        "and": "&#32;i",
        "faq": "FAQ",
        "disclaimers": "Prawne informacyje",
        "disclaimerpage": "Project:Prawne informacyje",
        "edithelp": "Pōmoc we edycyji",
+       "helppage-top-gethelp": "Pōmoc",
        "mainpage": "Przodniŏ zajta",
        "mainpage-description": "Przodniŏ strōna",
        "policy-url": "Project:Prawidła",
        "hidetoc": "schrůń",
        "collapsible-collapse": "Skryj",
        "collapsible-expand": "Pokŏż",
-       "thisisdeleted": "Pokoż/wćepej nazod $1",
+       "thisisdeleted": "Pokŏzać abo stworzić zaś $1?",
        "viewdeleted": "Uobejrzij $1",
        "restorelink": "{{PLURAL:$1|jedna wyćepano wersyjo|$1 wyćepane wersyje|$1 wyćepanych wersyjůw}}",
        "feedlinks": "Kanały:",
        "filereadonlyerror": "Ńy idźe pomjyńać plika \"$1\" abo repozytorjum \"$2\" terozki je zawarte.\n\nAdministrator kery zawarł wćepał kůmyntorz: \"$3\".",
        "invalidtitle-knownnamespace": "Felerne mjano \"$3\" w przestrzeńy \"$2\".",
        "invalidtitle-unknownnamespace": "Felerne mjano ze ńyznomům nůmerům raumu mjan $1: \"$2\"",
-       "exception-nologin": "Ńy jest żeś zalogůwany",
+       "exception-nologin": "Niy je żeś wlogowany(ŏ)",
        "exception-nologin-text": "Prosza [[Special:Userlogin|zaloguj śe]] coby mjeć mogebność przejśćo do tyj zajty abo akcyji.",
        "virus-badscanner": "Felerno konfiguracyjo – ńyznany skaner antywirusowy ''$1''",
        "virus-scanfailed": "skanowańy ńyudone (feler $1)",
        "nav-login-createaccount": "Logowańy / Tworzyńy kůnta",
        "logout": "Wyloguj",
        "userlogout": "Uodloguj śe",
-       "notloggedin": "Ńy jeżeś zalogowany",
+       "notloggedin": "Niy je żeś wlogowany(ŏ)",
        "userlogin-noaccount": "Niy mŏsz kōnta?",
        "userlogin-joinproject": "Dołōncz do {{GRAMMAR:D.lp|{{SITENAME}}}}",
        "createaccount": "Twōrz nowe kōnto",
        "userlogin-resetpassword-link": "Niy pamiyntŏsz hasła?",
        "userlogin-helplink2": "Pōmoc przi logowaniu",
        "userlogin-loggedin": "Zalogowano kej {{GENDER:$1|$1}}. Użyj formulara půńiżyj, coby zalogować śe kej inkszy używocz.",
-       "userlogin-createanother": "Twůrz inksze kůnto",
-       "createacct-emailrequired": "E-brif",
+       "userlogin-createanother": "Stwōrz inksze kōnto",
+       "createacct-emailrequired": "Adresa e‐mail",
        "createacct-emailoptional": "Adresa e-mail (niymusowo)",
        "createacct-email-ph": "Wkludź swojã adresã e-mail",
-       "createacct-another-email-ph": "Nastow e-brif",
-       "createaccountmail": "Użyj chwilowygo hasła losowo genyrowanygo a wyślij je na wrychtowany adres e-brifa.",
-       "createacct-realname": "Prawdźiwe imje a nazwisko (uopcjůnalńe)",
-       "createacct-reason": "Powůd:",
-       "createacct-reason-ph": "Pojakymu tworzisz nowe kůnta",
+       "createacct-another-email-ph": "Wkludź adresã e-mail",
+       "createaccountmail": "Użyj tymczasowego losowego hasła i wyślij je na wkludzōnõ adresã e-mail",
+       "createacct-realname": "Prŏwdziwe miano i nazwisko (niymusowo)",
+       "createacct-reason": "PowÅ\8dd",
+       "createacct-reason-ph": "Czymu tworzisz nowe kōnto",
        "createacct-submit": "Stwōrz kōnto",
        "createacct-another-submit": "Twůrz inksze kůnto",
        "createacct-benefit-heading": "{{grammar:B.lp|{{SITENAME}}}} tworzōm ludzie jak Ty.",
        "userexists": "Mjano użytkowńika, kere żeś wybroł, je zajynte. Wybjer, prosza, inksze mjano.",
        "loginerror": "Feler przi logowańu",
        "createacct-error": "Feler tworzyńo kůnta",
-       "createaccounterror": "Ńy możno stworzić konta $1",
+       "createaccounterror": "Niy idzie stworzić kōnta $1",
        "nocookiesnew": "Kůnto użytkowńika zostoło utworzůne, nale ńy jeżeś zalůgowany. {{SITENAME}} używo ćosteczek do logůwańo. Mosz wyłůnczone ćosteczka. Coby śe zalůgować, uodymknij ćosteczka a podej mjano a hasło swojigo kůnta.",
        "nocookieslogin": "{{SITENAME}} używo ćosteczek do logowańo użytkowńikůw. Mosz zablokowano jejich uobsłůga. Sprůbuj zaś kej załůnczysz uobsłůga ćosteczek.",
        "nocookiesfornew": "Konto sprowjorza ńy uostoło stworzone. Sprawdź, co mosz uodymkńynto uobsługa cookies.",
        "wrongpasswordempty": "Hasło kere żeś podou je uostawjůne blank. Naszkryflej je jeszcze roz.",
        "passwordtooshort": "Hasło kere żeś podoł je felerne abo za krůtke.\nHasło muśi mjeć przinojmńij {{PLURAL:$1|1 buchsztaba|$1 buchsztabůw}} a być inksze uod mjana użytkowńika.",
        "password-name-match": "Hasło mo być inksze atoli mjano używocza.",
-       "password-login-forbidden": "Mogebność wyboru tygo mjana używocza abo hasła je zawarte.",
+       "password-login-forbidden": "Używanie tego miana ôd używŏcza i hasła było zakŏzane.",
        "mailmypassword": "Wyczyść hasło",
        "passwordremindertitle": "Nowe tymczasowe hasło lo {{SITENAME}}",
        "passwordremindertext": "Ftoś (cheba Ty, ze IP $1)\npado, aże chce nowe hasło do {{SITENAME}} ($4).\nLo użytkowńika \"$2\" wygenyrowano nowe hasło a je ńim \"$3\".\nJak chćołżeś gynał to zrobjyć, to zalůgůj śe terozki a podej swoje hasło.\nHasło wygaśnie za {{PLURAL:$5|1 dzień|$5 dni}}.\n\nJak ftoś inkszy chćoł nowe hasło abo jak Ci śe przipůmńouo stare a ńy chcesz nowygo, to zignoruj to a używej starygo hasła.",
        "retypenew": "Naszkryflej jeszcze roz nowe hasło:",
        "resetpass_submit": "Nasztaluj hasło a zaloguj",
        "changepassword-success": "Twoje hasło zostoło půmyślńy půmjyńone!",
+       "botpasswords": "Hasła bota",
        "resetpass_forbidden": "Ńy idźe sam půmjyńyć hasłůw.",
-       "resetpass-no-info": "Muśisz być zalogowany, coby uzyskoć bezpostrzedńi dostymp do tyj zajty.",
+       "resetpass-no-info": "Żeby mieć bezpostrzedni dostymp do tyj strōny, trzeba sie zalogować.",
        "resetpass-submit-loggedin": "Zmjyń hasło",
        "resetpass-submit-cancel": "Uodćepej",
        "resetpass-wrong-oldpass": "Felerne tymczasowe abo aktualne hasło.\nMożliwe co właśńy zmjyńiłżeś swoje hasło abo poprosiłżeś uo nowe tymczasowe hasło.",
        "passwordreset-disabled": "No tyj wiki zamkńynto resytowańy hasył.",
        "passwordreset-username": "Miano ôd używŏcza:",
        "passwordreset-domain": "Domyna:",
-       "passwordreset-email": "E-brif:",
+       "passwordreset-email": "Adresa e‐mail:",
        "passwordreset-emailtitle": "Kůnto na {{GRAMMAR:MS.lp|{{SITENAME}}}}",
        "passwordreset-emailtext-ip": "Ftoś (cheba Ty, s IP $1)\npado, aże chce informacyji lo konta do {{GRAMMAR:MS.lp{{SITENAME}}}} ($4).\nZe tym ausdrukym sům powjůnzane kůnta:\n$2\n\n{{PLURAL:$3|Tymczasowygo hasła|Tymczasowych hasył}} możno użyć we {{PLURAL:$5|jedyn dźyń|$5 dńi}}.\n\nJak chćołżeś gynał to zrobjyć, to zaloguj śe terozki a podej swoje hasło.\n\nJak ftoś inkszy chćoł nowe hasło abo jak Ci śe przipůmńoło stare a ńy chcysz nowygo, to zignoruj to a używej starygo hasła.",
        "passwordreset-emailelement": "Mjano sprowjorza: \n$1\n\nTymczasowe hasło: \n$2",
        "changeemail-header": "Pomjyno ausduku e-mail",
        "changeemail-no-info": "Muśisz być zalogowany, coby uzyskać bezpostrzedńi dostymp do tyj zajty.",
        "changeemail-oldemail": "Uobecny ausdruk:",
-       "changeemail-newemail": "Nowy adresu e-brif",
+       "changeemail-newemail": "Nowŏ e-mailowŏ adresa:",
        "changeemail-none": "podstawowo",
        "changeemail-submit": "Spamjyntej nowy",
        "resettokens": "Resetuj tokeny",
        "minoredit": "To je małŏ zmiana",
        "watchthis": "Ôbserwuj tã strōnã",
        "savearticle": "Spamiyntej",
+       "publishpage": "Ôpublikuj strōnã",
+       "publishpage-start": "Ôpublikuj strōnã...",
        "preview": "Podglōnd",
        "showpreview": "Pokŏż podglōnd",
        "showdiff": "Pokŏż zmiany",
        "anoneditwarning": "<strong>Pozōr:</strong> Niy je żeś wlogowany(ŏ). Jak zrobisz jakeś zmiany, to Twoja adresa IP bydzie publicznie widać. Jeźli <strong>[$1 sie wlogujesz]</strong> abo <strong>[$2 stworzisz kōnto]</strong>, to Twoje zmiany bydōm przipisane do kōnta społym ze inkszymi profitami.",
-       "anonpreviewwarning": "Ńy jeżeś zalogowany. Twój IP ausdruk uostańy spamjyntany, eli ty bydźesz sprowjać zajte.",
+       "anonpreviewwarning": "<em>Niy je żeś wlogowany(ŏ). Spamiyntanie zmian zapisze twoja adresa IP we historyji edycyji tyj strōny.</em>",
        "missingsummary": "'''Pozůr:''' Ńy wprowadźůł żeś uopisu pomjyńań. Kej go ńy chcesz wprowadzać, naćiś knefel Spamjyntej jeszcze roz.",
-       "missingcommenttext": "Wćepej kůmyntorz půńiżyj.",
+       "missingcommenttext": "Wkludź kōmyntŏrz niżyj.",
        "missingcommentheader": "'''Dej pozůr:''' Treść nagłůwka je blank - uzupełńij go! Jeli tego ńy zrobisz, Twůj kůmyntorz bydźe naszkryflony bez nagłůwka.",
        "summary-preview": "Podglůnd uopisu:",
        "subject-preview": "Podglůnd tyjmy/nagłůwka:",
        "yourtext": "Twůj tekst",
        "storedversion": "Naszkryflano wersyjo",
        "editingold": "'''Dej pozůr: Sprowjosz inkszo wersyjo zajty kej bjeżůnco. Jeli jům naszkryflosz, wszyjske půźńyjsze pomjyńańa bydům wyćepane.'''",
-       "yourdiff": "RůżÅ\84ice",
+       "yourdiff": "RÅ\8dżnice",
        "copyrightwarning": "Pamjyntej uo tym, aże cołki wkłod do {{SITENAME}} udostympńůmy wedle zasad $2 (dokładńij we $1). Jak ńy chcesz, coby kożdy můg go půmjyńać a dalij rozpowszychńoć, ńy wćepuj uůnygo sam. Szkryflajůnc sam tukej pośwjadczosz tyż, co te pisańy je twoje własne, abo żeś go wźůn(a) ze materjołůw kere sům na ''public domain'', abo kůmpatybilne.<br />\n'''PROSZA ŃY WĆEPYWAĆ SAM MATYRJOŁŮW KERE SŮM CHRŮŃONE AUTORSKIM PRAWYM BEZ DOZWOLEŃO WŁAŚĆIĆELA!'''",
        "copyrightwarning2": "Pamjyntej uo tym, aże cołki wkłod do {{GRAMMAR:MS.lp|{{SITENAME}}}} może być sprowjany, pomjyńany abo wyćepany bez inkszych użytkownikůw. Jak ńy chcysz, coby kożdy můg uůnygo zmjyńać a dalij rozpowszychńoć bez uograniczyń, ńy wćepuj go sam.<br />\nSzkryflajůnc sam tukej pośwjadczosz tyż, co te pisańy je twoje własne, abo żeś go wźůn(a) ze matyrjołůw kere sům na public domain, abo kůmpatybilne (kuknij tyż: $1).\n'''PROSZA ŃY WĆEPYWAĆ SAM MATYRJOŁŮW KERE SŮM CHRŮŃONE PRAWYM AUTORSKIM BEZ DOZWOLEŃO WŁAŚĆIĆELA!'''",
        "longpageerror": "''Feler: Tekst kery żeś sam wćepywoł mo {{PLURAL:$1|jedyn kilobajt|$1 kilobajtůw}}. Maksymalno dugość tekstu ńy może być srogszo kej {{PLURAL:$2|jedyn kilobajt|$2 kilobajtůw}}. Twůj tekst ńy bydźe sam naszkryflany.'''",
        "readonlywarning": "'''Dej pozůr: Baza danych zostoua filowo zawarto skuli potřeb admińistracyjnych. Bestůž ńy do śe terozki naškryflać Twojich pomjyńań. Radzymy přećepać nowy tekst kajś do plika tekstowego (wytnij/wklej) a wćepać sam zaś po uodymkńyńću bazy.'''\n\nAdmińistrator kery zawar baza dou take wyjaśńyńe: $1",
        "protectedpagewarning": "'''Dej pozůr: Sprowjańe tyj zajty zostoło zawarte. Mogům jům sprowjać ino użytkowńicy ze uprawńyńami admińistratora.'''\nUostatńy wpis w rejerze je poniżej.",
-       "semiprotectedpagewarning": "'''Pozůr:''' Ta zajta zostoÅ\82a zawarto a ino zaregiszterowani użytkownicy mogům jům sprowjaÄ\87.\nUostotÅ\84y wpis w rejerze je Å\84yżej.",
+       "semiprotectedpagewarning": "'''PozÅ\8dr:''' Ta strÅ\8dna je zawartÅ\8f i ino zaregistrowani używÅ\8fcze mogÅ\8dm jÅ\8dm edytowaÄ\87.\nÃ\94statni wpis ze regestu je niżyj.",
        "cascadeprotectedwarning": "'''Dej pozůr:''' Ta zajta zostoła zawarto a ino użytkowńicy ze uprawńyńami admińistratora mogům jům sprowjać. Zajta ta je podpjynto pod {{PLURAL:$1|nastympujůnco zajta, kero zostoła zawarto|nastympujůncych zajtach, kere zostouy zawarte}} ze załůnczonům uopcjům dźedźiczyńo:",
        "titleprotectedwarning": "'''Dej pozůr: Zajta uo tym titlu zostoła zawarto a ino [[Special:ListGroupRights|ńykerzi użytkowńicy]] mogům jům wćepać.'''\nUostatńy wpis z rejera je ńyżej.",
        "templatesused": "{{PLURAL:$1|Muster użyty|Mustry użyte}} na tyj strōnie:",
        "permissionserrorstext-withaction": "Niy mŏsz przizwolyniŏ na $2, skuli {{PLURAL:$1|takigo powodu|takich powodōw}}:",
        "recreate-moveddeleted-warn": "<strong>Pozōr: Prziwrŏcŏsz strōnã, co była przōdzij skasowanŏ.</strong>\n\nDej pozōr, czy prziwrōcynie tyj strōny je nŏleżne.\nRegesty kasowań i pōnkniyńć tyj strōny idzie ôbejzdrzeć niżyj.",
        "moveddeleted-notice": "Ta strōna była skasowanŏ.\nRegest skasowań, zabezpieczyń i pōnkniyńć tyj strōny je pokŏzany niżyj.",
-       "log-fulllog": "Ukoż rejer",
+       "log-fulllog": "Pokoż cołki regest",
        "edit-hook-aborted": "Sprowjyńy sztopńynte skiż hoka.\nŃy je wjadůme pů jakymu.",
        "edit-gone-missing": "Ńy idźe zaktualizować zajty.\nZdowo śe, co zostoła wyćepano.",
        "edit-conflict": "Kůnflikt sprowjyń.",
        "revisiondelete": "Wyćep/wćep nazod wersyje",
        "revdelete-nooldid-title": "Ńy wybrano wersyji",
        "revdelete-nooldid-text": "Ńy wybrano wersyji na kerych mo zostać wykůnano ta uoperacyjo.",
-       "revdelete-no-file": "Ńy mo tygo plika.",
+       "revdelete-no-file": "Tyn zbiōr niy istniyje.",
        "revdelete-show-file-confirm": "Jeżeś echt pewny co chcesz uobejzdrzeć wyćepano wersyjo plika „<nowiki>$1</nowiki>” s $2 $3?",
        "revdelete-show-file-submit": "Ja",
        "logdelete-selected": "{{PLURAL:$1|Wybrane zdarzyńy ze rejeru|Wybrane zdarzyńa ze rejeru}}:",
        "search-nonefound": "Żŏdne wyniki niy ôdpowiadajōm tymu zapytaniu.",
        "powersearch-legend": "Sznupańy zaawansowane",
        "powersearch-ns": "Sznupej we przestrzyńach mjan:",
-       "powersearch-togglelabel": "Uoznocz:",
+       "powersearch-togglelabel": "Ôznŏcz:",
        "powersearch-toggleall": "Wszyjsko",
-       "powersearch-togglenone": "żodno",
+       "powersearch-togglenone": "Nic",
        "search-external": "Zewnyntrzne sznupańy",
        "searchdisabled": "Sznupańy we {{GRAMMAR:MS.lp|{{SITENAME}}}} uostoło zawarte. Ńim go uotworzům nazod, moges sprůbować sznupańo bez Google. Ino zauważ, co informacyje uo treśći {{GRAMMAR:MS.lp|{{SITENAME}}}} můgům być we Google ńyaktuelne.",
        "search-error": "Wystůmpjůł feler przi sznupańu: $1",
        "preferences": "Preferyncyje",
        "mypreferences": "Preferyncyje",
-       "prefs-edits": "Liczba sprowjyń:",
+       "prefs-edits": "Liczba edycyji:",
        "prefs-skin": "Skůrka",
        "skin-preview": "podglůnd",
        "datedefault": "Důmyślny",
        "prefs-watchlist-token": "ID pozůrlisty:",
        "prefs-misc": "Roztůmajte",
        "prefs-resetpass": "Zmjyń hasło",
-       "prefs-changeemail": "Pomjyno ausdruka e-brif",
-       "prefs-setemail": "Nastow e-brif",
-       "prefs-email": "Uopcyje e-brifa",
-       "prefs-rendering": "Wyglůnd",
+       "prefs-changeemail": "Zmiyń abo skasuj adresã e-mail",
+       "prefs-setemail": "Nasztaluj adresã e-mail",
+       "prefs-email": "Ôpcyje e-maila",
+       "prefs-rendering": "WyglÅ\8dnd",
        "saveprefs": "Spamjyntej",
-       "restoreprefs": "Wćep wszyjskie důmyślne preferencyje",
+       "restoreprefs": "Prziwrōć wszyjske wychodne preferyncyje (we wszyjskich sekcyjach)",
        "prefs-editing": "Sprowjańy",
        "searchresultshead": "Sznupańy",
        "stub-threshold": "Maksymalny rozmjar artikla uoznaczanygo kej <a href=\"#\" class=\"stub\">stub (kůnsek)</a>",
        "prefs-namespaces": "Raumy mjan",
        "default": "důmyślńy",
        "prefs-files": "Pliki",
-       "youremail": "E-brif:",
+       "youremail": "E-mail:",
        "username": "{{GENDER:$1|Mjano używocza}}:",
        "prefs-memberingroups": "Należy do {{PLURAL:$1|grupy|grup:}}",
-       "prefs-registration": "Czas twůrzyńa kůnta:",
+       "prefs-registration": "Data registracyje:",
        "yourrealname": "Prawdźiwe mjano",
        "yourlanguage": "Godka interfejsu",
-       "yournick": "Twoja szrajbka:",
+       "yournick": "Nowy podpis:",
        "badsig": "Felerny podpis, wejzdrzij na znaczniki HTML.",
        "badsiglength": "Twojo szrajbka je za dugo. Ji maksymalno dugość to $1 {{PLURAL:$1|buchsztaby|buchsztabůw}}",
        "yourgender": "Płeć:",
        "gender-unknown": "ńyznano",
        "gender-male": "chop",
        "gender-female": "baba",
-       "email": "E-brif",
+       "email": "E‐mail",
        "prefs-help-realname": "* Mjano a nazwisko (uopcjůnalńy): jak żeś zdecydowoł aże je podosz, bydům użyte, coby Twoja robota mjoła atrybucyjo.",
        "prefs-help-email": "Ukozańy e-brifowygo adresu ńy je powinne, nale nutne, coby resetować ausdruk, eli zapomńisz.",
        "prefs-help-email-others": "Mogesz tyż doć mogebność inkszym używoczům posłać ci e-brif bez twojo zajta używocza abo zajta dyskusyje. Twůj e-brifowy adres śe ńy ukoże.",
        "prefs-help-email-required": "Wymogany je adres e-brifa.",
        "prefs-diffs": "Diffy",
-       "userrights": "Zarzůndzańy prowami użytkowńikůw",
+       "userrights": "Prawa ôd używŏczōw",
        "userrights-lookup-user": "Zarzůndzej prowami użytkownika",
        "userrights-user-editname": "Wkludź sam mjano użytkowńika:",
        "editusergroup": "Sprowjej grupy użytkowńika",
        "right-suppressredirect": "Ńy twůrz przekerowańo ze starygo mjana jak przećepujesz zajta",
        "right-upload": "Wćepane pliki",
        "right-reupload": "Nadpisuj pliki kere sam już sům wćepane",
-       "right-reupload-own": "Nadpisuj plik wćepany sam bez tygo samygo użytkowńika",
+       "right-reupload-own": "Nadpisowanie przōdzij przisłanych zbiorōw przed siebie",
        "right-reupload-shared": "Nadpisuj pliki umjeszczůne we repozytorjům dźelůnych plikůw na lokalnyj kopje",
        "right-upload_by_url": "Wćepńij sam plik ze adresa URL",
        "right-purge": "Wyczyść pamjyńć podrynczno do zajty za wyjůntkym zajty potwjerdzyńo",
        "right-deleterevision": "Wyćepywańy a wćepywańy nazod wskazanych sprowjyń zajtůw",
        "right-deletedhistory": "Pokazuj historyjo usuńyntych sprowjyń, bez tekstu uopisu",
        "right-browsearchive": "Sznupej za wyćepanymi zajtůma",
-       "right-undelete": "Wćepej nazod wyćepano zajta",
+       "right-undelete": "Prziwrŏcanie skasowanych strōn",
        "right-suppressrevision": "Přyglůndańy i uodtwařańy sprowjyń schrůńůnych před admińistratorami",
        "right-suppressionlog": "Pokoż prywatne lůgi",
        "right-block": "Zawjyrańy sprowjorzům mogebnośći edytowańo",
        "action-move": "přećepańe tyj zajty",
        "action-move-subpages": "přećepańo tyj zajty uoroz s jeij podzajtůma",
        "action-move-rootuserpages": "Překludzańy zajtůw uod užytkowńikůw (nale bes jeich podzajtůw)",
-       "action-movefile": "przećepańe tygo plika",
-       "action-upload": "wćepńyńćo tygo plika",
-       "action-reupload": "nadpisańo tygo plika",
-       "action-reupload-shared": "nadpisańo tygo plika we wspůlnym repozytorjům",
-       "action-upload_by_url": "wćepańo tygo plika s adresa URL",
+       "action-movefile": "pōnkniyńcie tego zbioru",
+       "action-upload": "przisłanie tego zbioru",
+       "action-reupload": "nadpisanie tego zbioru",
+       "action-reupload-shared": "nadpisanie tego zbioru we spōlnym repozytorium",
+       "action-upload_by_url": "zaladowanie tego zbioru ze adresy URL",
        "action-writeapi": "naškryflańo bez interfejs API",
        "action-delete": "wyćepańo tyj zajty",
        "action-deleterevision": "wyćepańo tyj wersyje",
        "action-undelete": "wćepańo nazod tyj zajty",
        "action-suppressrevision": "podglůndu a wćepańo nazod tyj wersyje schrůńůnyj",
        "action-suppressionlog": "podglůndu rejera schrůńańo",
-       "action-block": "zawarća uod sprowjyń tygo spowjořa",
+       "action-block": "zablokowanie edytowaniŏ tymu używŏczowi",
        "action-protect": "zmiany poziōmōw zabezpieczyń na tyj strōnie",
        "action-import": "importu tyj zajty s inkšyj wiki",
        "action-importupload": "importu tyj zajty bez wćepańe plika",
        "recentchanges-label-plusminus": "Strōna zmiyniyła srogość ô tela bajtōw",
        "recentchanges-legend-heading": "<strong>Legynda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ôbejzdrzij tyż [[Special:NewPages|listã nowych strōn]])",
+       "rcfilters-legend-heading": "<strong>Wykŏz skrōtōw:</strong>",
        "rcfilters-other-review-tools": "Inksze nŏrzyńdzia kōntrole",
        "rcfilters-filter-humans-label": "Czowiek (niy bot)",
        "rcfilters-liveupdates-button": "Aktualizacyje na żywo",
        "upload": "Zaladuj zbiōr",
        "uploadbtn": "Prziślij zbiōr",
        "reuploaddesc": "Nazod do formulařa uod wćepywańo.",
-       "uploadnologin": "Ńy jest žeś zalogůwany",
+       "uploadnologin": "Niy je żeś wlogowany(ŏ)",
        "uploadnologintext": "Muśyš śe [[Special:UserLogin|zalůgować]] ńim wćepńeš pliki.",
        "upload_directory_missing": "Katalog lo wćepywanych plikůw ($1) ńy istńeje a serwer WWW ńy poradźi go utwořić.",
        "upload_directory_read_only": "Serwer ńy može škryflać do katalůgu ($1) kery je přeznačůny na wćepywane pliki.",
        "uploaddisabled": "Wćepywanie sam plikůw je zawarte",
        "uploaddisabledtext": "Wćepywańe plikůw je zawarte.",
        "uploadscripted": "Tyn plik zawjyro kod HTML abo skrypt kery može zostać felerńe zinterpretowany bez přyglůndarka internetowo.",
-       "uploadvirus": "W tym pliku je wirus! Ščygůuy: $1",
+       "uploadvirus": "W zbiorze je wirus!\nInformacyje: $1",
        "upload-source": "Plik zdrzůdłowy",
        "sourcefilename": "Mjano oryginalne:",
        "destfilename": "Mjano docylowe:",
        "linkstoimage": "{{PLURAL:$1|Ta strōna używŏ|Te strōny używajōm}} tego zbioru:",
        "linkstoimage-more": "Tyn zbiōr {{PLURAL:$1|używŏ wiyncyj niż jedna strōna|używajōm wiyncyj niż $1 strōny|używŏ wiyncyj niż $1 strōn}}.\nTa lista pokazuje ino {{PLURAL:$1|piyrszõ|piyrsze $1}}.\n[[Special:WhatLinksHere/$2|Połnŏ lista]] je tyż dostympnŏ.",
        "nolinkstoimage": "Żŏdnŏ strōna niy używŏ tego zbioru.",
-       "morelinkstoimage": "Pokož [[Special:WhatLinksHere/$1|wjyncy uodnośnikůw]] do tygo plika.",
+       "morelinkstoimage": "Pokŏż [[Special:WhatLinksHere/$1|wiyncyj linkōw]] do tego zbioru.",
        "linkstoimage-redirect": "$1 (przekerowanie do zbioru) $2",
        "duplicatesoffile": "{{PLURAL:$1|Nastympujůncy plik je kopjům|Nastympujůnce pliki sům kopjůma}} tygo plika:",
        "sharedupload": "Tyn plik je wćepńynty na $1 a inksze projekty tyż go mogům używać.",
        "sharedupload-desc-here": "Tyn zbiōr je ze $1 i może być używany we inkszych projektach.\nÔpis na jego [$2 strōnie ôpisu zbioru] je pokŏzany niżyj.",
        "filepage-nofile": "Niy ma zbioru ze tym mianym.",
-       "uploadnewversion-linktext": "Wćepńij nowšo wersyjo tygo plika",
+       "uploadnewversion-linktext": "Zaladuj nowszõ wersyjõ tego zbioru",
        "upload-disallowed-here": "Niy możesz podmiynić tego zbioru.",
        "filerevert": "Přiwracańy $1",
        "filerevert-legend": "Přiwracańy poprzedńy wersje plika",
        "mimetype": "Typ MIME:",
        "download": "pobier",
        "unwatchedpages": "Zajty na kere ńy je dowany pozůr",
-       "listredirects": "Lista překerowań",
-       "unusedtemplates": "Ńyužywane šablôny",
+       "listredirects": "Lista przekerowań",
+       "unusedtemplates": "Niyużywane mustry",
        "unusedtemplatestext": "Půńižej znojdowo śe lista wšyjstkich zajtůw s přestřyńi mjan {{ns:template}}, kere ńy sům užywane bez inkše zajty. Sprowdź inkše adresowańa ku šablůnům, ńim wyćepńeš ta zajta.",
        "unusedtemplateswlh": "ku adresatu",
        "randompage": "Losowŏ strōna",
        "statistics-users": "Zarejerowanych użytkowńikůw",
        "statistics-users-active": "Aktywnych użytkowńikůw",
        "statistics-users-active-desc": "Użytkowńiki, kere bůły aktywne bez {{PLURAL:$1|uostatńi dźyń|uostatńich $1 dńi}}",
-       "doubleredirects": "Podwůjne překierowańa",
+       "pageswithprop": "Strōny ze włŏsnościami",
+       "pageswithprop-legend": "Strōny ze włŏsnościami",
+       "doubleredirects": "Tuplowane przekerowania",
        "doubleredirectstext": "Na tyi liśće mogům znojdować śe překerowańo pozorne. Uoznača to, aže půńižej pjyrwšej lińii artikla, zawjerajůncyj \"#REDIRECT ...\", može znojdować śe dodotkowy tekst. Koždy wjerš listy zawjero uodwouańo do pjyrwšygo i drůgygo překerowańo a pjyrwšom lińjům tekstu drůgygo překerowańo. Uůmožliwjo to na ogůu uodnaleźyńy wuaśćiwygo artikla, do kerygo powinno śe překerowywać.",
        "double-redirect-fixed-move": "zajta [[$1]] zostoła zastůmpjůno bez przekerowańy, skiż jeij przekludzyńo ku [[$2]]",
        "double-redirect-fixer": "Korektōr przekerowań",
-       "brokenredirects": "Zuomane překerowańa",
+       "brokenredirects": "Złōmane przekerowania",
        "brokenredirectstext": "Překerowańo půńižej wskazujům na artikle kerych sam ńy ma.",
        "brokenredirects-edit": "sprowjéj",
        "brokenredirects-delete": "wyćep",
-       "withoutinterwiki": "Artikle bez interwiki",
+       "withoutinterwiki": "Artykuły bez interwiki",
        "withoutinterwiki-summary": "Zajty půńižej ńy majům uodwouań do wersjůw w inkšych godkach.",
        "withoutinterwiki-legend": "Prefiks",
        "withoutinterwiki-submit": "Pokož",
-       "fewestrevisions": "Zajty z nojmńijšom ilośćům wersyji",
+       "fewestrevisions": "Strōny, co majōm nojmynij wersyji",
        "nbytes": "$1 {{PLURAL:$1|bajt|bajty|bajtōw}}",
        "ncategories": "$1 {{PLURAL:$1|kategoryja|kategorje|kategorjůw}}",
        "nlinks": "$1 {{PLURAL:$1|link|linki|linkůw}}",
        "nmembers": "$1 {{PLURAL:$1|elymynt|elymynta|elymyntōw}}",
        "nrevisions": "$1 {{PLURAL:$1|wersja|wersje|wersjůw}}",
        "specialpage-empty": "Ta zajta je pusto.",
-       "lonelypages": "Poćepńynte zajty",
+       "lonelypages": "Sierocie strōny",
        "lonelypagestext": "Do zajtůw půńiżyj ńy adresuje żodno inkszo zajta we {{SITENAME}}.",
-       "uncategorizedpages": "Zajty bez kategoryje",
-       "uncategorizedcategories": "Kategoryje bez kategoriůw",
-       "uncategorizedimages": "Pliki bez kategoryjůw",
-       "uncategorizedtemplates": "Mustry bez kategorii",
-       "unusedcategories": "Ńyużywane kategoryje",
-       "unusedimages": "Ńyużywane pliki",
-       "wantedcategories": "Potrzebne katygoryje",
-       "wantedpages": "Nojpotrzebńijsze zajty",
-       "wantedfiles": "Potrzebne pliki",
-       "wantedtemplates": "Potrzebne szablůny",
+       "uncategorizedpages": "Niyskategoryzowane strōny",
+       "uncategorizedcategories": "Kategoryje bez kategoryji",
+       "uncategorizedimages": "Niyskategoryzowane zbiory",
+       "uncategorizedtemplates": "Niyskategoryzowane mustry",
+       "unusedcategories": "Niyużywane kategoryje",
+       "unusedimages": "Niyużywane zbiory",
+       "wantedcategories": "Potrzebne kategoryje",
+       "wantedpages": "Potrzebne strōny",
+       "wantedfiles": "Potrzebne zbiory",
+       "wantedtemplates": "Potrzebne mustry",
        "mostlinked": "Nojczyńśćij adresowane",
        "mostlinkedcategories": "Kategoryje we kerych je nojwjyncyj artikli",
        "mostlinkedtemplates": "Nojczyńśćij adresowane mustry",
        "mostimages": "Nojczyńśćij adresowane pliki",
        "mostrevisions": "Nojczyńśćij sprowjane artikle",
        "prefixindex": "Wszyjske strōny ze prefiksym",
-       "shortpages": "Nojkrůtsze zajty",
-       "longpages": "Duge artikle",
-       "deadendpages": "Artikle bez linkůw",
+       "shortpages": "NojkrÅ\8dtsze strÅ\8dny",
+       "longpages": "Duge strōny",
+       "deadendpages": "Ślepe strōny",
        "deadendpagestext": "Zajty wymjyńůne půńiżyj ńy majům uodnośńikůw do żodnych inkszych zajtůw kere sům na tyj wiki.",
-       "protectedpages": "Zawarte zajty",
+       "protectedpages": "Zastawiōne strōny",
        "protectedpages-indef": "Ino zabezpjeczyńo ńyuokreślůne",
        "protectedpages-cascade": "Yno zajty zabezpjeczůne rekursywńy",
        "protectedpagesempty": "Żodno zajta ńy je terozki zawarto ze podanymi parametrami.",
-       "protectedtitles": "Zawarte mjana artikli",
+       "protectedtitles": "Zastawiōne tytuły",
        "protectedtitlesempty": "Do tych štalowań utwořyńy artikla uo dowolnym mjańy ńy je zawarte",
        "listusers": "Lista używŏczōw",
        "listusers-editsonly": "Pokoż yno użytkowńikůw kere majům sprowjyńa",
        "usercreated": "{{GENDER:$3|Utworzono}} $1 uo $2",
        "newpages": "Nowe strōny",
        "newpages-username": "Miano ôd używŏcza:",
-       "ancientpages": "Nojstarše artikle",
+       "ancientpages": "Nojstarsze strōny",
        "move": "Przeniyś",
        "movethispage": "Přećepej ta zajta",
        "unusedimagestext": "Pamjyntej, proša, aže inkše witryny, np. projekty Wikimedja w inkšych godkach, můgům adresować do tych plikůw užywajůnc bezpośredńo URL. Bez tůž ńykere ze plikůw můgům sam być na tej liśće pokozane mimo, aže žodna zajta ńy adresuje do ńich.",
        "all-logs-page": "Wszyjske óperacyje",
        "alllogstext": "Spōlne pokŏzanie wszyjskich dostympnych regestōw {{SITENAME}}.\nMożesz uakuratnić widok bez ôbranie zorty regestu, miana ôd używŏcza, abo tykanyj strōny (dŏwŏ pozōr na małe i sroge litery).",
        "logempty": "Niy ma we regeście zgodliwych elymyntōw.",
-       "log-title-wildcard": "Šnupej za titlami kere začynojům śe uod tygo tekstu",
+       "log-title-wildcard": "Szukej tytułōw, co sie zaczynajōm ôd tego tekstu",
        "allpages": "Wszyjske strōny",
-       "nextpage": "Nostympno zajta ($1)",
-       "prevpage": "Popředńo zajta ($1)",
+       "nextpage": "Nastympnŏ strōna ($1)",
+       "prevpage": "Poprzedniŏ strōna ($1)",
        "allpagesfrom": "Strōny, co sie zaczynajōm ôd:",
-       "allpagesto": "Zajty uo titlach kere na zadku majům:",
+       "allpagesto": "Pokŏż strōny do:",
        "allarticles": "Wszyjske strōny",
        "allinnamespace": "Wszyjstke zajty (we przestrzyńi mjan $1)",
        "allpagessubmit": "Idź",
        "linksearch-text": "Idźe użyć symbola wjeloznacznygo „*”. Lů bajszpila „*.wikipedia.org” spowoduje sznupańy za wszyjstkimi linkůma kere prowadzům ku důmyńy „wikipedia.org” a jeij poddůmyn.<br />\nUobsůgiwane protokoły: $1",
        "linksearch-line": "$1 link na zajće $2",
        "linksearch-error": "Symbola wjeloznacznygo idźe użyć yno na anfangu mjana hosta.",
-       "listusersfrom": "Pokaž užytkowńikůw začynojůnc uod:",
+       "listusersfrom": "Pokŏż używŏczōw ôd:",
        "listusers-submit": "Uobejrzij",
        "listusers-noresult": "Ńy znejdźůno žodnygo užytkowńika.",
+       "activeusers": "Lista aktywnych używŏczōw",
        "activeusers-noresult": "Niy szło znŏjść żŏdnych używŏczōw",
        "listgrouprights": "Uprawniynia grup używŏczōw",
        "listgrouprights-summary": "Niżyj widać wykŏz grup używŏczōw zdefiniowanych na tyj wiki, społym ze jejich prawami dostympu.\n[[{{MediaWiki:Listgrouprights-helppage}}|Ekstra informacyje]] ô uprawniyniach.",
        "listgrouprights-key": "* <span class=\"listgrouprights-granted\">Dane uprawńyńy</span>\n* <span class=\"listgrouprights-revoked\">Uodebrane uprawńyńy</span>",
        "listgrouprights-group": "Grupa",
        "listgrouprights-rights": "Uprawniynia",
-       "listgrouprights-helppage": "Help:Uprawńyńo grup użytkowńikůw",
+       "listgrouprights-helppage": "Help:Prawa grup używŏczōw",
        "listgrouprights-members": "(lista czōnkōw grupy)",
        "listgrouprights-addgroup": "Idźe dodać do {{PLURAL:$2|grupy|grup}}: $1",
        "listgrouprights-removegroup": "Idźe wyćepać s {{PLURAL:$2|grupy|grup}}: $1",
        "listgrouprights-addgroup-all": "Idźe dodać do kożdyj grupy",
        "listgrouprights-removegroup-all": "Idźe wyćepać s wszyjstkich grup",
        "listgrouprights-addgroup-self": "Je mogebny dać swe konto do {{PLURAL:$2|grupy|grup:}} $1",
+       "listgrants": "Prawa",
+       "trackingcategories": "Kategoryje, co śledzōm",
        "mailnologin": "Brak adresu",
        "mailnologintext": "Muśyš śe [[Special:UserLogin|zalůgować]] i mjeć wpisany aktualny adres e-brif w swojich [[Special:Preferences|preferyncyjach]], coby můc wysuać e-brif do inkšygo užytkowńika.",
        "emailuser": "Poślij tymu używŏczowi e-mail",
        "emailpagetext": "Możesz użyć půńiższygo formularza, coby wysłać wjadůmość e-brif do tygo użytkowńika.\nAdres e-brifa, kery zostoł bez Ćebje wkludzůny we [[Special:Preferences|Twojich sztalowańach]], pojawi śe we polu „Uod”, bez cůż uodbjorca bydźe můg Ći uodpedźeć.",
        "defemailsubject": "{{SITENAME}} - e-mail ôd używŏcza \"$1\"",
        "usermaildisabled": "E-mail ôd używŏcza je zastŏwiōny",
-       "noemailtitle": "Brak adresu e-brif",
+       "noemailtitle": "Brak adresy e-mail",
        "noemailtext": "Tyn używŏcz niy podoł nŏleżnyj adresy email.",
        "nowikiemailtext": "Tyn używŏcz niy chce dostŏwać emaili ôd inkszych.",
        "emailtarget": "Podej adresata",
        "email-legend": "Wyślij e-brif ku inkszymu użytkowńikowi {{GRAMMAR:MS.lp|{{SITENAME}}}}",
        "emailfrom": "Uod:",
        "emailto": "Ku:",
-       "emailsubject": "Tyjma:",
+       "emailsubject": "Tymat:",
        "emailmessage": "Nowina:",
        "emailsend": "Wyślij",
        "emailccme": "Wyślij mi kopja moiy wjadomości.",
        "watchlistfor2": "{{GENDER:$1|Używŏcza|Używŏczki}} $1 $2",
        "nowatchlist": "Ńy ma žodnych pozycyji na liśće zajtůw, na kere dowoš pozůr.",
        "watchlistanontext": "$1 coby uobejřeć abo sprowjać elymynty listy zajtůw, na kere dowoš pozůr",
-       "watchnologin": "Ńy jest žeś zalůgowany",
+       "watchnologin": "Niy je żeś wlogowany(ŏ)",
        "addedwatchtext": "Zajta \"[[:$1]]\" zostoua dodano do Twojij [[Special:Watchlist|listy artiklůw, na kere dowoš pozůr]].\nNa tyi liśće bydźeš mjou rejer přišuych sprowjyń tyi zajty i jeji zajty godki, a mjano zajty bydźeš mjou škryflane '''tustym''' na [[Special:RecentChanges|liśće půmjyńanych na ůostatku]], cobyś mjou wygoda w jei pomjyńańa filować.",
        "removedwatchtext": "Artikel \"[[:$1]]\" zostou wyćepńjynty s [[Special:Watchlist|Twojij pozorlisty]].",
        "watch": "Ôbserwuj",
        "confirm": "Potwjyrdź",
        "excontent": "zawartość zajty „$1”",
        "excontentauthor": "treść: „$1” (jedyny aůtor: [[Special:Contributions/$2|$2]])",
-       "exbeforeblank": "popředńo zawartość uobecńy pustej zajty: „$1”",
+       "exbeforeblank": "zawartość przed ôprōzniyniym: „$1”",
        "delete-confirm": "Wyćep „$1”",
        "delete-legend": "Wyćep",
        "historywarning": "Pozor! Ta zajta kerům chceš wyćepnůńć mo historjo:",
        "dellogpage": "Regest kasowań",
        "dellogpagetext": "To je lista uostatńo wykůnanych wyćepań.",
        "deletionlog": "rejer wyćepań",
-       "reverted": "Přiwrůcůno popředńo wersyja",
+       "reverted": "Prziwrōcōnŏ poprzedniõ wersyjõ",
        "deletecomment": "Čymu:",
        "deleteotherreason": "Inkšy powůd:",
        "deletereasonotherlist": "Inkszy powůd",
        "editcomment": "Sprowjyńe uopisano: <em>$1</em>.",
        "revertpage": "Wycofano sprowjyńe użytkowńika [[Special:Contributions/$2|$2]] ([[User talk:$2|godka]]). Autor prziwrůcůnej wersyji to [[User:$1|$1]].",
        "rollback-success": "Wycofano sprowjyńa użytkowńika $1.\nPrziwrůcůno uostatńo wersyja autorstwa  $2.",
-       "sessionfailure": "Feler weryfikacyji zalůgowańo.\nPolecyńy zostoło anulowane, coby ůńiknůńć przechwycyńo sesyji.\n\nNaćiś knefel „cofej”, przeładuj zajta, a potym zaś wydej polecyńy",
+       "sessionfailure": "Wyglōndŏ na to, że je problym ze twojōm sesyjōm logowaniŏ;\nta ôperacyjŏ była zastawiōnŏ jako zabezpieczynie przed przejyńciym sesyje.\nPrziślij formular zaś.",
        "protectlogpage": "Regest zawarć",
        "protectlogtext": "Půńižej znojdowo śe lista zawarć i uodymkńjyńć pojydynčych zajtůw.\nCoby přejřeć lista uobecńy zawartych zajtůw, přeńdź na zajta wykazu [[Special:ProtectedPages|zawartych zajtůw]].",
        "protectedarticle": "zawar „[[$1]]”",
        "undeletedpage": "'''Wćepano nazod zajta $1.'''\n\nUobejřij [[Special:Log/delete|rejer wyćepań]], kejbyś chćou přeglůndnůnć uostatnie uoperacyje wyćepywańo i wćepywańo nazod zajtůw.",
        "undelete-header": "Uobejřij [[Special:Log/delete|rejer wyćepań]] coby sprawdźić uostatńo wyćepane zajty.",
        "undelete-search-box": "Šnupej za wyćepńjyntymi zajtami",
-       "undelete-search-prefix": "Zajty začynajůnce śe uod:",
+       "undelete-search-prefix": "Strōny, co sie zaczynajōm ôd:",
        "undelete-search-submit": "Šnupej",
        "undelete-no-results": "Ńy znejdźono wskazanych zajtůw we archiwum wyćepanych.",
        "undelete-filename-mismatch": "Ńy idźe wćepać nazod wersyji plika z datům $1: ńyzgodność mjana plika",
        "ipbcreateaccount": "Ńy dozwůl utwožyć kůnta",
        "ipbemailban": "Zawrzij mogebność wysůłańo e-brifůw",
        "ipbenableautoblock": "Zawřij uostatńi adres IP tygo užytkowńika i autůmatyčńy wšyjstke kolejne, s kerych bydźe průbowou sprowjać zajty",
-       "ipbsubmit": "Zawřij uod sprowjyń tygo užytkowńika",
+       "ipbsubmit": "Zablokuj tego używŏcza",
        "ipbother": "Ikszy czas",
        "ipboptions": "2 godziny:2 hours,1 dziyń:1 day,3 dni:3 days,1 tydziyń:1 week,2 tydnie:2 weeks,1 miesiōnc:1 month,3 miesiōnce:3 months,6 miesiyncy:6 months,1 rok:1 year,na dycki:infinite",
        "ipbhidename": "Schrůń mjano użytkowńika/adres IP w rejerze zawarć, na liśće aktywnych zawarć i liśće użytkowńikůw",
-       "ipbwatchuser": "Dowej pozůr na zajta uosobisto i zajta godki tygo užytkowńika",
+       "ipbwatchuser": "Ôbserwuj włŏsnõ strōnã i strōnã dyskusyje ôd tego używŏcza",
        "ipb-change-block": "Zmjyń sztalowańa zawarća uod sprowjyń",
        "badipaddress": "Felerny adres IP",
        "blockipsuccesssub": "Zawarće uod sprowjyń udane",
        "ipusubmit": "Uodymkńij sprowjyńo užytkowńikowi",
        "unblocked": "[[User:$1|$1]] zostou uodymkńynty.",
        "unblocked-id": "Zawarće $1 zostouo zdjynte",
+       "blocklist": "Zablokowani używocze",
+       "autoblocklist": "Autōmatyczne blokady",
        "ipblocklist": "Zawarte używocze",
        "ipblocklist-legend": "Znejdź zawartygo uod sprawjyń užytkowńika",
        "ipblocklist-submit": "Šnupej",
        "anononlyblock": "ino ńyzalůgowańy",
        "noautoblockblock": "autůmatyčne zawjyrańy uod sprowjyń wůuůnčůne",
        "createaccountblock": "zawarto twořyńe kont",
-       "emailblock": "zawarty e-brif",
+       "emailblock": "e-mail zablokowany",
        "blocklist-nousertalk": "ńy mogům sprowjać własnych zajtůw godki",
        "ipblocklist-empty": "Lista zawarć je pusto.",
        "ipblocklist-no-results": "Podany adres IP abo užytkowńik ńy je zawarty uod sprowjyń.",
        "block-log-flags-anononly": "ino anůnimowi",
        "block-log-flags-nocreate": "tworzynie kōnta je zastawiōne",
        "block-log-flags-noautoblock": "autůmatyczne zawjerańy uod sprawjyń wyłůnczůne",
-       "block-log-flags-noemail": "e-brif zawarty",
+       "block-log-flags-noemail": "e-mail zablokowany",
        "block-log-flags-nousertalk": "ńy może sprowjać włosnyj zajty godki",
        "block-log-flags-angry-autoblock": "rozszerzůne automatyczne zawjyrańe załůnczůne",
        "range_block_disabled": "Možliwość zawjerańo zakresu adresůw IP zostoua wůuůnčůno.",
        "movepage-moved": "'''\"$1\" przećiśńjynto ku \"$2\"'''",
        "articleexists": "Artikel ze takym mjanym już je, abo mjano je złe.\nWybjer inksze mjano.",
        "cantmove-titleprotected": "Ńy możesz przećepnůńć zajty, beztuż co jeij nowe mjano je ńydozwolůne skuli zabezpjeczyńo przed utworzyńym",
-       "movetalk": "Przećiś godke, jak możno.",
+       "movetalk": "Przeniyś zwiōnzanõ ze strōnōm dyskusyjõ",
        "move-subpages": "Přećepńij podzajty",
        "move-talk-subpages": "Jeli je to możliwe przekludź wszyjstke zajty godki podzajtůw",
        "movepage-page-exists": "Zajta $1 już istńeje a ńy idźe jeij autůmatyczńy nadszkryflać.",
        "import-noarticle": "Ńy ma zajtůw do zaimportowańo!",
        "import-nonewrevisions": "Wšyjstke wersyje zostouy juž wčeśńij zaimportowane.",
        "xml-error-string": "$1 lińa $2, kolůmna $3 (bajt $4): $5",
-       "import-upload": "Wćepej dane XML",
+       "import-upload": "Prziślij dane XML",
        "import-token-mismatch": "Straćiły śe dane ze sesyje. Prosza spróbować zaś.",
        "import-invalid-interwiki": "Ńy idźe importować s podanyj wiki.",
        "importlogpage": "Regest importōw",
        "tooltip-diff": "Pokŏż zmiany zrobiōne we tekście.",
        "tooltip-compareselectedversions": "Ôbejzdrzij rōżnice miyndzy dwōma ôbranymi wersyjami tyj strōny",
        "tooltip-watch": "Przidej tyn artykuł do ôbserwowanych",
-       "tooltip-recreate": "Wćepej nazod zajta mimo aže bůua wčeśńij wyćepano.",
+       "tooltip-recreate": "Stwōrz strōnã zaś, chociŏż była skasowanŏ",
        "tooltip-upload": "Rozpočyńće wćepywańa",
        "tooltip-rollback": "\"Cŏfej\" jednym klikniyńciym cŏfie wszyjske zmiany ôd ôstatnigo używŏcza.",
        "tooltip-undo": "\"Cŏfnij\" cŏfie tã edycyjõ i ôtwiyrŏ ôkno edycyje we trybie podglōndu.\nDozwolŏ na wkludzynie powodu we ôpisie.",
        "bad_image_list": "Dane trza wćepać we formaće:\n\nJyno tajle listy (lińije, kere śe napoczynajům uod *) absztychujemy.\nPjyrszy link we lińiji muśi być linkym do zakozanygo pliku.\nDolsze linki we lińiji sům uwożane za wyjimki  – sům to mjana zajtůw, na kerych idzie użyć plik ze zakozanym mjanym.",
        "metadata": "Metadane",
        "metadata-help": "We tym zbiorze sōm ekstra informacyje pewnikym przidane ôd fotoaparatu abo skanera użytego do zrobiyniŏ abo zdigitalizowaniŏ go.\nJeźli zbiōr bōł modyfikowany, niykere informacyje mogōm niy cołkym ôdpowiadać zmodyfikowanymu zbiorowi.",
-       "metadata-expand": "Pokož ščygůuy",
-       "metadata-collapse": "Schowej ščygůuy",
+       "metadata-expand": "Pokŏż rozszyrzōne informacyje",
+       "metadata-collapse": "Skryj rozszyrzōne informacyje",
        "metadata-fields": "Metadane ôbrazōw wymianowane we tyj wiadōmości bydōm pokazowane na strōnie grafiki po zwiniyńciu tabule metadanych.\nInksze pola bydōm wychodnie skryte.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "namespacesall": "wszyjske",
        "monthsall": "wszyjske",
        "scarytranscludetoolong": "[za dugo adresa URL]",
        "deletedwhileediting": "'''Pozůr''': Ta zajta zostoła wyćepano po tym, jak żeś rozpoczůł jej sprowjańy!",
        "confirmrecreate": "Užytkowńik [[User:$1|$1]] ([[User talk:$1|godka]]) wyćepnůu tyn artikel po tym jak žeś rozpočůu(eua) jygo sprowjańe, podajůnc kej powůd wyćepańo:\n: ''$2''\nPotwjerdź chęć wćepańo nazod tygo artikla.",
-       "recreate": "Wćepej nazod",
+       "recreate": "Stwōrz zaś",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Wyčyśćić pamjyńć podrynčnům do tyi zajty?",
        "confirm-purge-bottom": "Uodśwjyżeńy zajty wyczyśći pamjyńć podrynczno a wymuśi pokozańy jeij aktualnyj wersyji.",
-       "imgmultipageprev": "← popředńo zajta",
+       "imgmultipageprev": "← poprzedniŏ strōna",
        "imgmultipagenext": "nastympnŏ strōna →",
        "imgmultigo": "Idź!",
        "imgmultigoto": "Idź do strōny $1",
-       "table_pager_next": "Nostympno zajta",
-       "table_pager_prev": "Popředńo zajta",
+       "table_pager_next": "Nastympnŏ strōna",
+       "table_pager_prev": "Poprzedniŏ strōna",
        "table_pager_first": "Pjyrwšo zajta",
        "table_pager_last": "Uostatńo zajta",
        "table_pager_limit": "Pokož $1 pozycyji na zajće",
        "fileduplicatesearch-result-n": "We {{GRAMMAR:MS.lp|{{SITENAME}}}} {{PLURAL:$2|je dodatkowo kopia|sům $2 dodatkowe kopje|je $2 dodatkowych kopii}} plika „$1”.",
        "specialpages": "Ekstra strōny",
        "specialpages-note-restricted": "* Ekstra zajty uogůlńy dostympne.\n* <strong class=\"mw-specialpagerestricted\">Ekstra zajty do kerych dostymp je uograńiczůny.</strong>",
-       "specialpages-group-maintenance": "Raporty kůnserwacyjne",
+       "specialpages-group-maintenance": "Reporty kōnserwacyjne",
        "specialpages-group-other": "Inkše ekstra zajty",
-       "specialpages-group-login": "Logowańy / regisztrowańy",
+       "specialpages-group-login": "Logowanie / registracyjŏ",
        "specialpages-group-changes": "Pomjyńane na uostatku a rejery",
        "specialpages-group-media": "Pliki",
-       "specialpages-group-users": "Użytkowńiki i uprawńyńa",
+       "specialpages-group-users": "Używŏcze i uprawniynia",
        "specialpages-group-highuse": "Zajty čynsto užywane",
-       "specialpages-group-pages": "Listy zajt",
+       "specialpages-group-pages": "Listy strōn",
        "specialpages-group-pagetools": "Nořyńdźa zajtůw",
        "specialpages-group-wiki": "Informacyje a werkcojgi wiki",
        "specialpages-group-redirects": "Ekstra zajty, kere kerujům",
        "searchsuggest-search": "Szukej we {{SITENAME}}",
        "duration-days": "$1 {{PLURAL:$1|dziyń|dni}}",
        "expand_templates_ok": "OK",
-       "randomrootpage": "Losowŏ strōna (bez podstrōn)"
+       "randomrootpage": "Losowŏ strōna (bez podstrōn)",
+       "changecredentials": "Zmiyń poświadczynia",
+       "changecredentials-submit": "Zmiyń poświadczynia"
 }
index e64f8fa..f9c80aa 100644 (file)
        "protectedpagetext": "ఈ పేజీలో మార్పులు వగైరాలు చెయ్యకుండా ఉండేందుకు గాను, సంరక్షించబడింది.",
        "viewsourcetext": "మీరీ పేజీ సోర్సును చూడవచ్చు, కాపీ చేసుకోవచ్చు.",
        "viewyourtext": "ఈ పేజీలో <strong>మీరు చేసిన మార్పుల</strong> మూలాన్ని చూడవచ్చు, కాపీ చేసుకోవచ్చు.",
-       "protectedinterface": "à°\88 à°ªà±\87à°\9cà±\80, à°\88 à°µà°¿à°\95à±\80 à°¯à±\8aà°\95à±\8dà°\95 à°¸à°¾à°«à±\8dà°\9fà±\81à°µà±\87à°°à±\81 à°\87à°\82à°\9fà°°à±\81à°«à±\87à°¸à±\81à°\95à±\81 à°\9aà±\86à°\82దిన à°\9fà±\86à°\95à±\8dà°¸à±\8dà°\9fà±\81à°¨à±\81 à°\85à°\82దిసà±\8dà°¤à±\81à°\82ది. à°¦à±\81à°¶à±\8dà°\9aà°°à±\8dయల à°¨à°¿à°µà°¾à°°à°£ à°\95à±\8bసమà±\88 à°¦à±\80à°¨à±\8dని à°¸à°\82à°°à°\95à±\8dà°·à°¿à°\82à°\9aà°¾à°\82. à°µà°¿à°\95à±\80లనà±\8dనిà°\9fà°¿à°²à±\8bà°¨à±\81 అనువాదాలను చేర్చాలన్నా, మార్చాలన్నా మీడియావికీ స్థానికీకరణ ప్రాజెక్టైన [https://translatewiki.net/ translatewiki.net] ను వాడండి.",
+       "protectedinterface": "à°\88 à°ªà±\87à°\9cà±\80, à°\88 à°µà°¿à°\95à±\80 à°¯à±\8aà°\95à±\8dà°\95 à°\87à°\82à°\9fà°°à±\81à°«à±\87à°¸à±\81à°\95à±\81 à°\9aà±\86à°\82దిన à°\9fà±\86à°\95à±\8dà°¸à±\8dà°\9fà±\81à°¨à±\81 à°\85à°\82దిసà±\8dà°¤à±\81à°\82ది. à°¦à±\81à°¶à±\8dà°\9aà°°à±\8dయలనà±\81 à°¨à°¿à°µà°¾à°°à°¿à°\82à°\9aà±\87à°\82à°¦à±\81à°\95à±\88 à°¦à±\80à°¨à±\8dని à°¸à°\82à°°à°\95à±\8dà°·à°¿à°\82à°\9aà°¾à°\82. à°µà°¿à°\95à±\80లనà±\8dనిà°\9fà°¿à°²à±\8b అనువాదాలను చేర్చాలన్నా, మార్చాలన్నా మీడియావికీ స్థానికీకరణ ప్రాజెక్టైన [https://translatewiki.net/ translatewiki.net] ను వాడండి.",
        "editinginterface": "<strong>హెచ్చరిక:</strong> సాఫ్టువేరుకు ఇంటరుఫేసు టెక్స్టును అందించేందుకు పనికొచ్చే పేజీని మీరు సరిదిద్దుతున్నారు.\nఈ పేజీలో చేసే మార్పుల వల్ల ఇతర వాడుకరులకు కనబడే ఇంటరుఫేసు ప్రభావితమౌతుంది.",
        "translateinterface": "అన్ని వికీలలో కనిపించేలా అనువాదాలు చేర్చాలన్నా, మార్చాలన్నా, దయచేసి [https://translatewiki.net/ translatewiki.net] ను వాడండి. ఇది మీడియావికీ స్థానికీకరణ ప్రాజెక్టు.",
        "cascadeprotected": "కింది {{PLURAL:$1|పేజీని|పేజీలను}} కాస్కేడింగు ఆప్షనుతో సంరక్షించబడింది. ప్రస్తుత పేజీ, ఈ పేజీల్లో ట్రాన్స్‌క్లూడు అయి ఉంది కాబట్టి, దిద్దుబాటు చేసే వీలు లేకుండా ఇది కూడా రక్షణలో ఉంది:\n$2",
index 4735c4c..821ce9d 100644 (file)
        "nocreate-loggedin": "Yeni sayfalar oluşturmaya yetkiniz yok.",
        "sectioneditnotsupported-title": "Bölüm değiştirmesi desteklenmiyor",
        "sectioneditnotsupported-text": "Bölüm değiştirmesi bu sayfada desteklenmiyor.",
+       "modeleditnotsupported-title": "Düzenleme desteklenmemektedir",
        "permissionserrors": "İzin hatası",
        "permissionserrorstext": "Aşağıdaki {{PLURAL:$1|sebep|sebepler}}den dolayı, bunu yapmaya yetkiniz yok:",
        "permissionserrorstext-withaction": "Aşağıdaki {{PLURAL:$1|neden|nedenler}}den dolayı $2 yetkiniz yok:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Boş nesne",
        "content-json-empty-array": "Boş dizi",
+       "unsupported-content-model": "<strong>Uyarı:</strong> İçerik modeli $1 desteklenmemektedir",
+       "unsupported-content-diff": "İçerik modeli $1 için değişiklikler desteklenmemektedir.",
+       "unsupported-content-diff2": "İçerik modeli  $1 ve $2 arasındaki değişiklikler bu vikide desteklenmemektedir.",
        "deprecated-self-close-category": "Kendiliğinden geçersiz HTML etiketlerini kullanan sayfalar",
        "deprecated-self-close-category-desc": "Sayfa, <code>&lt;b/></code> veya <code>&lt;span/></code> gibi kendiliğinden geçersiz HTML etiketleri içeriyor. Bunların davranışları yakında HTML5 belirtimiyle tutarlı olacak şekilde değişecektir, bu nedenle wikitext'deki kullanımları, kullanımdan kaldırılır.",
        "duplicate-args-warning": "<strong>Uyarı:</strong>[[:$1]] [[:$2]] şablonunu \"$3\" parametresi için birden fazla değerle çağırıyor. Sadece sağlanan son değer kullanılacak.",
        "undo-norev": "Değişiklik geri alınamaz çünkü ya silinmiş ya da varolmamaktadır.",
        "undo-nochange": "Düzeltme zaten geri alınmış.",
        "undo-summary": "$1 değişikliği [[Special:Contributions/$2|$2]] ([[User talk:$2|mesaj]]) tarafından geri alındı.",
+       "undo-summary-anon": "[[Special:Contributions/$2|$2]] tarafından yapılan değişiklik $1'i geri al",
        "undo-summary-username-hidden": "Gizli bir kullanıcı tarafından $1 sürümü geri alınıyor",
        "cantcreateaccount-text": "Bu IP adresinden ('''$1''') kullanıcı hesabı oluşturulması [[User:$3|$3]] tarafından engellenmiştir.\n\n$3 tarafından verilen sebep ''$2''",
        "cantcreateaccount-range-text": "<strong>$1</strong> aralığındaki IP'ler için hesap oluşturma [[User:$3|$3]] tarafından engellendi, bu sizin IP adresinizi de (<strong>$4</strong>) içeriyor.\n\n$3 tarafından verilen gerekçe <em>$2</em>",
        "backend-fail-contenttype": "\"$1\" konumunda saklanan dosyanın içerik türü belirlenemiyor.",
        "backend-fail-batchsize": "Depolama arkaplan uygulamasına $1 dosya {{PLURAL:$1|işlemi|işlemi}} yığını verildi; sınır $2 {{PLURAL:$1|işlem|işlem}}.",
        "backend-fail-usable": "Yetersiz izinlerden ya da eksik dizin/konteynerlerden dolayı \"$1\" dosyası okunup yazılamıyor.",
+       "backend-fail-stat": "Dosya \"$1\" durumu okunamıyor.",
+       "backend-fail-hash": "Dosya \"$1\"e ait kriptografi belirlenemedi.",
        "filejournal-fail-dbconnect": "\"$1\" depolama arkaplan uygulaması için günlük veri tabanına erişilemiyor.",
        "filejournal-fail-dbquery": "\"$1\" depolama arkaplan uygulaması için günlük veri tabanı güncellenemiyor.",
        "lockmanager-notlocked": "\"$1\" kilidi açılamıyor; kilitli değil.",
index 16310eb..bda0a4f 100644 (file)
        "grouppage-suppress": "{{ns:project}}:Назирләр",
        "right-read": "Битләрне карау",
        "right-edit": "Битләрне үзгәртү",
-       "right-createpage": "биÑ\82лÓ\99Ñ\80 Ñ\8fÑ\81аÑ\83 (бÓ\99Ñ\85Ó\99Ñ\81 Ð±Ñ\83лмаганнаÑ\80Ñ\8bн)",
-       "right-createtalk": "бÓ\99Ñ\85Ó\99Ñ\81 Ð±Ð¸Ñ\82ен Ñ\8fÑ\81аÑ\83",
-       "right-createaccount": "яңа кулланучы хисап язмасын ясау",
+       "right-createpage": "Ð\91иÑ\82лÓ\99Ñ\80не Ñ\82өзү (бÓ\99Ñ\85Ó\99Ñ\81 Ð±Ð¸Ñ\82лÓ\99Ñ\80е Ð±Ñ\83лмаган)",
+       "right-createtalk": "Ð\91Ó\99Ñ\85Ó\99Ñ\81 Ð±Ð¸Ñ\82лÓ\99Ñ\80ен Ñ\82өзү",
+       "right-createaccount": "Яңа кулланучы хисап язмасын төзү",
        "right-minoredit": "\"кече төзәтмә\" тамгасын кую",
        "right-move": "Битләрне күчерү",
        "right-move-subpages": "Битләрне асбитләр белән бергә күчерү",
index df3af16..519a355 100644 (file)
        "nocreate-loggedin": "У вас нема дозволу створювати нові сторінки.",
        "sectioneditnotsupported-title": "Редагування окремих розділів не підтримується",
        "sectioneditnotsupported-text": "На цій сторінці не підтримується редагування окремих розділів",
+       "modeleditnotsupported-title": "Редагування не підтримується",
+       "modeleditnotsupported-text": "Редагування не підтримується для моделі вмісту $1.",
        "permissionserrors": "Помилка доступу",
        "permissionserrorstext": "У вас нема прав на виконання цієї операції з {{PLURAL:$1|1=наступної причини|наступних причин}}:",
        "permissionserrorstext-withaction": "У Вас нема дозволу на $2 з {{PLURAL:$1|1=такої причини|таких причин}}:",
-       "contentmodelediterror": "Ð\92и Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82е Ñ\80едагÑ\83ваÑ\82и Ñ\86Ñ\8e Ð²ÐµÑ\80Ñ\81Ñ\96Ñ\8e, Ð¾Ñ\81кÑ\96лÑ\8cки Ð¼Ð¾Ð´ÐµÐ»Ñ\8c Ð¹Ð¾Ð³Ð¾ Ð·Ð¼Ñ\96Ñ\81Ñ\82Ñ\83 â\80\94  <code>$1</code>, Ð²Ñ\96дÑ\80Ñ\96знÑ\8fÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ð²Ñ\96д Ñ\82епеÑ\80Ñ\96Ñ\88нÑ\8cоÑ\97 Ð¼Ð¾Ð´ÐµÐ»Ñ\96 Ð·місту сторінки — <code>$2</code>.",
+       "contentmodelediterror": "Ð\92и Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82е Ñ\80едагÑ\83ваÑ\82и Ñ\86Ñ\8e Ð²ÐµÑ\80Ñ\81Ñ\96Ñ\8e, Ð¾Ñ\81кÑ\96лÑ\8cки Ð¼Ð¾Ð´ÐµÐ»Ñ\8c Ð¹Ð¾Ð³Ð¾ Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83 â\80\94  <code>$1</code>, Ð²Ñ\96дÑ\80Ñ\96знÑ\8fÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ð²Ñ\96д Ñ\82епеÑ\80Ñ\96Ñ\88нÑ\8cоÑ\97 Ð¼Ð¾Ð´ÐµÐ»Ñ\96 Ð²місту сторінки — <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Попередження: Ви намагаєтеся створити сторінку, яка раніше вже була вилучена.'''\n\nПеревірте, чи Вам справді потрібно створювати цю сторінку.\nНижче, для зручності, наведений журнал вилучень і перейменувань:",
        "moveddeleted-notice": "Цю сторінку було вилучено.\nДля довідки нижче наведені відповідні записи з журналів вилучень, захисту й перейменувань цієї сторінки.",
        "moveddeleted-notice-recent": "На жаль, ця сторінка нещодавно була вилучена (протягом останніх 24 годин). Для довідки нижче наведені відповідні записи з журналів вилучень, захисту й перейменувань цієї сторінки.",
        "invalid-content-data": "Неприпустимі дані",
        "content-not-allowed-here": "Вміст «$1» недопустимий на сторінці [[:$2]] у слоті «$3»",
        "editwarning-warning": "Перехід на іншу сторінку призведе до втрати ваших змін.\nЯкщо ви ввійшли до системи, то ви можете відключити це попередження в розділі \"{{int:prefs-editing}}\" ваших налаштувань.",
-       "editpage-invalidcontentmodel-title": "Ð\9aонÑ\82енÑ\82на Ð¼Ð¾Ð´ÐµÐ»Ñ\8c не підтримується",
+       "editpage-invalidcontentmodel-title": "Ð\9cоделÑ\8c Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83 не підтримується",
        "editpage-invalidcontentmodel-text": "Контентна модель «$1» не підтримується.",
        "editpage-notsupportedcontentformat-title": "Формат вмісту не підтримується",
        "editpage-notsupportedcontentformat-text": "Формат вмісту $1 не підтримується моделлю вмісту $2.",
        "content-model-css": "CSS",
        "content-json-empty-object": "Порожній об'єкт",
        "content-json-empty-array": "Порожній масив",
+       "unsupported-content-model": "<strong>Увага:</strong> Модель вмісту $1 не підтримується у цій вікі.",
+       "unsupported-content-diff": "Різниця між версіями не підтримується моделлю вмісту $1.",
+       "unsupported-content-diff2": "Зміни між моделями вмісту $1 та $2 не підтримуються у цій вікі.",
        "deprecated-self-close-category": "Сторінки, що використовують недійсні самозакривні теги HTML",
        "deprecated-self-close-category-desc": "Сторінка містить недійсні самозакривні теги HTML, наприклад, <code>&lt;b/></code> чи <code>&lt;span/></code>.  Їхню поведінку невдовзі буде змінено у відповідності зі специфікацією HTML5, тому їх використання у вікітексті є застарілим.",
        "duplicate-args-warning": "<strong>Увага:</strong> [[:$1]] викликає [[:$2]] з більш ніж одним значенням параметра «$3». Буде використано лише останнє вказане значення.",
        "backend-fail-contenttype": "Не вдалося визначити тип вмісту файла, щоб зберегти його в \"$1\".",
        "backend-fail-batchsize": "Серверна частина отримала блок із $1 {{PLURAL:$1|1=файлової операції|файлових операцій}}; обмеження складає $2 {{PLURAL:$2|файлову операцію|файлові операції|файлових операцій}}.",
        "backend-fail-usable": "Файл «$1» не може бути прочитано чи записано через недостатні повноваження або відсутність каталогів (контейнерів).",
+       "backend-fail-stat": "Не вдалось прочитати статус файлу «$1».",
+       "backend-fail-hash": "Неможливо визначити криптографічний хеш файлу «$1».",
        "filejournal-fail-dbconnect": "Не вдалося підключитися до бази даних журналу для сховища «$1».",
        "filejournal-fail-dbquery": "Не вдалося оновити базу даних журналу для сховища «$1».",
        "lockmanager-notlocked": "Не вдалося розблокувати \"$1\"; він не заблокований.",
        "changecontentmodel-emptymodels-title": "Немає доступних моделей коментарів",
        "changecontentmodel-emptymodels-text": "Вміст сторінки [[:$1]] не може бути перетворений до будь якого типу.",
        "log-name-contentmodel": "Журнал змін моделі вмісту",
-       "log-description-contentmodel": "Ð\9dа Ñ\86Ñ\96й Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\96 Ð¿ÐµÑ\80елÑ\96Ñ\87енÑ\96 Ð·Ð¼Ñ\96ни Ð´Ð¾ ÐºÐ¾Ð½Ñ\82енÑ\82ниÑ\85 Ð¼Ð¾Ð´ÐµÐ»ÐµÐ¹ Ñ\81Ñ\82оÑ\80Ñ\96нок, Ð° Ñ\82акож Ñ\81Ñ\82оÑ\80Ñ\96нки, Ñ\81Ñ\82воÑ\80енÑ\96 Ð· Ð½ÐµÑ\81Ñ\82андаÑ\80Ñ\82ноÑ\8e ÐºÐ¾Ð½Ñ\82енÑ\82ноÑ\8e Ð¼Ð¾Ð´ÐµÐ»Ð»Ñ\8e.",
+       "log-description-contentmodel": "Ð\9dа Ñ\86Ñ\96й Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\96 Ð¿ÐµÑ\80елÑ\96Ñ\87енÑ\96 Ð·Ð¼Ñ\96ни Ð´Ð¾ Ð¼Ð¾Ð´ÐµÐ»ÐµÐ¹ Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83 Ñ\81Ñ\82оÑ\80Ñ\96нок, Ð° Ñ\82акож Ñ\81Ñ\82оÑ\80Ñ\96нки, Ñ\81Ñ\82воÑ\80енÑ\96 Ð· Ð½ÐµÑ\81Ñ\82андаÑ\80Ñ\82ноÑ\8e  Ð¼Ð¾Ð´ÐµÐ»Ð»Ñ\8e Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83.",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|створив|створила}} сторінку $3, використовуючи нестандартну модель вмісту «$5»",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|змінив|змінила}} модель вмісту сторінки $3 з «$4» на «$5»",
        "logentry-contentmodel-change-revertlink": "відкинути",
        "immobile-source-page": "Цю сторінку не можна перейменувати.",
        "immobile-target-page": "Не можна присвоїти сторінці цю назву.",
        "movepage-invalid-target-title": "Запитуване ім'я недопустиме.",
-       "bad-target-model": "Ð\9dеможливо Ð¿ÐµÑ\80еÑ\82воÑ\80иÑ\82и $1 Ð½Ð° $2: Ð½ÐµÑ\81Ñ\83мÑ\96Ñ\81нÑ\96 Ð¼Ð¾Ð´ÐµÐ»Ñ\96 Ð´Ð°Ð½Ð¸Ñ\85.",
+       "bad-target-model": "Ð\9dеможливо Ð¿ÐµÑ\80еÑ\82воÑ\80иÑ\82и $1 Ð½Ð° $2: Ð½ÐµÑ\81Ñ\83мÑ\96Ñ\81нÑ\96 Ð¼Ð¾Ð´ÐµÐ»Ñ\96 Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83.",
        "imagenocrossnamespace": "Неможливо дати файлові назву з іншого простору назв",
        "nonfile-cannot-move-to-file": "Не можна перейменовувати сторінки з інших просторів назв на файли",
        "imagetypemismatch": "Нове розширення файлу не співпадає з його типом",
        "import-error-interwiki": "Сторінку «$1» не було імпортовано, оскільки її назва зарезервована для зовнішніх посилань (interwiki).",
        "import-error-special": "Сторінку «$1» не було імпортовано, оскільки вона належить до особливого простору назв, що не дозволяє створення сторінок.",
        "import-error-invalid": "Сторінку «$1» не було імпортовано, оскільки назва, у яку вона імпортується, неприпустима у цій вікі.",
-       "import-error-unserialize": "Версія $2 сторінки «$1» не може бути деструктурованою (десеріалізованою). Отримано повідомлення, що у цій версії використано модель $3 сериалізована як $4.",
+       "import-error-unserialize": "Версія $2 сторінки «$1» не може бути деструктурованою (десеріалізованою). Отримано повідомлення, що у цій версії використано модель вмісту $3 сериалізовану як $4.",
        "import-error-bad-location": "Версія $2, що використовує модель вмісту $3, не може бути збережена у «$1» цієї вікі, тому що ця модель не підтримується на цій сторінці.",
        "import-options-wrong": "{{PLURAL:$2|1=Неправильна опція|Неправильні опції}}: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "Вказана некоректна назва кореневої сторінки",
        "sessionprovider-nocookies": "Куки можуть бути відключені. Переконайтеся, що у Вас включені cookies і почніть знову.",
        "randomrootpage": "Випадкова коренева сторінка",
        "log-action-filter-block": "Тип блокування:",
-       "log-action-filter-contentmodel": "Тип Ð·Ð¼Ñ\96ни ÐºÐ¾Ð½Ñ\82енÑ\82ноÑ\97 Ð¼Ð¾Ð´ÐµÐ»Ñ\96:",
+       "log-action-filter-contentmodel": "Тип Ð·Ð¼Ñ\96ни Ð¼Ð¾Ð´ÐµÐ»Ñ\96 Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83:",
        "log-action-filter-delete": "Тип вилучення:",
        "log-action-filter-import": "Тип імпорту:",
        "log-action-filter-managetags": "Тип дії з управління тегами:",
        "log-action-filter-block-block": "Блокування",
        "log-action-filter-block-reblock": "Зміна блокування",
        "log-action-filter-block-unblock": "Розблокування",
-       "log-action-filter-contentmodel-change": "Ð\97мÑ\96на ÐºÐ¾Ð½Ñ\82енÑ\82ноÑ\97 Ð¼Ð¾Ð´ÐµÐ»Ñ\96",
-       "log-action-filter-contentmodel-new": "СÑ\82воÑ\80еннÑ\8f Ñ\81Ñ\82оÑ\80Ñ\96нки Ð· Ð½ÐµÑ\81Ñ\82андаÑ\80Ñ\82ноÑ\8e ÐºÐ¾Ð½Ñ\82енÑ\82ноÑ\8e Ð¼Ð¾Ð´ÐµÐ»Ð»Ñ\8e",
+       "log-action-filter-contentmodel-change": "Ð\97мÑ\96на Ð¼Ð¾Ð´ÐµÐ»Ñ\96 Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83",
+       "log-action-filter-contentmodel-new": "СÑ\82воÑ\80еннÑ\8f Ñ\81Ñ\82оÑ\80Ñ\96нки Ð· Ð½ÐµÑ\81Ñ\82андаÑ\80Ñ\82ноÑ\8e Ð¼Ð¾Ð´ÐµÐ»Ð»Ñ\8e Ð²Ð¼Ñ\96Ñ\81Ñ\82Ñ\83",
        "log-action-filter-delete-delete": "Видалення сторінки",
        "log-action-filter-delete-delete_redir": "Перезапис перенаправлення",
        "log-action-filter-delete-restore": "Відновлення сторінки",
index 93db9ca..a65b321 100644 (file)
        "permanentlink": "固定链接",
        "permanentlink-revid": "修订版本ID",
        "permanentlink-submit": "前往修订版本",
-       "newsection": "新会话",
+       "newsection": "新章节",
        "newsection-page": "目标页面",
-       "newsection-submit": "跳转页",
+       "newsection-submit": "前往页面",
        "dberr-problems": "抱歉!本网站出现了一些技术问题。",
        "dberr-again": "请等待几分钟后重试。",
        "dberr-info": "(无法访问数据库:$1)",
index dd764d1..c2cd05b 100644 (file)
        "editcomment": "編輯摘要為:<em>$1</em>。",
        "revertpage": "已還原[[Special:Contributions/$2|$2]]([[User talk:$2|討論]])的編輯至最後由[[User:$1|$1]]所修訂的版本",
        "revertpage-nouser": "已還原隱藏使用者的編輯為最後 {{GENDER:$1|[[User:$1|$1]]}} 修訂的版本",
-       "rollback-success": "已還原 {{GENDER:$3|$1}} 所做的編輯;\n變更回由 {{GENDER:$4|$2}} 修訂的最後一個版本。",
+       "rollback-success": "已還原{{GENDER:$3|$1}}所做的編輯;變更回由{{GENDER:$4|$2}}修訂的最後一個版本。",
        "sessionfailure-title": "連線階段失敗",
        "sessionfailure": "您的登入連線階段似乎有問題,為了預防連線階段受到劫持攻擊,此動作已經被取消。請重新提交表單。",
        "changecontentmodel": "變更頁面的內容模型",
        "logentry-partialblock-block-ns": "{{PLURAL:$1|命名空間|命名空間}}$2",
        "logentry-partialblock-block": "$1{{GENDER:$2|已封鎖}}{{GENDER:$4|$3}}對於$7的編輯為期限至 $5 $6",
        "logentry-partialblock-reblock": "$1{{GENDER:$2|已變更}}在$7的{{GENDER:$4|$3}}禁止編輯封鎖設定為期限至 $5 $6",
-       "logentry-non-editing-block-block": "$1{{GENDER:$2|已封鎖}}{{GENDER:$4|$3}}的指定編輯操作至期限$5 $6",
+       "logentry-non-editing-block-block": "$1{{GENDER:$2|已封鎖}}{{GENDER:$4|$3}}的指定非編輯操作至期限$5$6",
        "logentry-non-editing-block-reblock": "$1{{GENDER:$2|已變更}}{{GENDER:$4|$3}}的指定非編輯操作之封鎖設定,到期時間為$5 $6",
        "logentry-suppress-block": "$1 {{GENDER:$2|已封鎖}} {{GENDER:$4|$3}} 期限為 $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|已變更}} {{GENDER:$4|$3}} 的封鎖設定期限為 $5 $6",
index 59e9c86..55bf54c 100644 (file)
@@ -9,3 +9,7 @@
  */
 
 $fallback = 'hy';
+
+$magicWords['redirect'] = [ '0', '#ՎԵՐԱՅՂՈՒՄ', '#ՎԵՐԱՀՂՈՒՄ', '#REDIRECT' ];
+
+$namespaceNames[NS_CATEGORY] = 'Ստորոգութիւն';
index 6edb7ec..3f0c3df 100644 (file)
--- a/load.php
+++ b/load.php
@@ -28,6 +28,8 @@ use MediaWiki\MediaWikiServices;
 // details of the session. Enforce this constraint with respect to session use.
 define( 'MW_NO_SESSION', 1 );
 
+define( 'MW_ENTRY_POINT', 'load' );
+
 require __DIR__ . '/includes/WebStart.php';
 
 // URL safety checks
index dcd147f..1a5daca 100644 (file)
@@ -236,63 +236,6 @@ SEARCHDATA_FILE        = searchdata.xml
 EXTERNAL_SEARCH_ID     =
 EXTRA_SEARCH_MAPPINGS  =
 #---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-GENERATE_LATEX         = NO
-LATEX_OUTPUT           = latex
-LATEX_CMD_NAME         = latex
-MAKEINDEX_CMD_NAME     = makeindex
-COMPACT_LATEX          = NO
-PAPER_TYPE             = a4wide
-EXTRA_PACKAGES         =
-LATEX_HEADER           =
-LATEX_FOOTER           =
-LATEX_EXTRA_FILES      =
-PDF_HYPERLINKS         = YES
-USE_PDFLATEX           = YES
-LATEX_BATCHMODE        = NO
-LATEX_HIDE_INDICES     = NO
-LATEX_SOURCE_CODE      = NO
-LATEX_BIB_STYLE        = plain
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-GENERATE_RTF           = NO
-RTF_OUTPUT             = rtf
-COMPACT_RTF            = NO
-RTF_HYPERLINKS         = NO
-RTF_STYLESHEET_FILE    =
-RTF_EXTENSIONS_FILE    =
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-GENERATE_MAN           = {{GENERATE_MAN}}
-MAN_OUTPUT             = man
-MAN_EXTENSION          = .3
-MAN_LINKS              = NO
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-GENERATE_XML           = NO
-XML_OUTPUT             = xml
-XML_PROGRAMLISTING     = YES
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-GENERATE_DOCBOOK       = NO
-DOCBOOK_OUTPUT         = docbook
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-GENERATE_AUTOGEN_DEF   = NO
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-GENERATE_PERLMOD       = NO
-PERLMOD_LATEX          = NO
-PERLMOD_PRETTY         = YES
-PERLMOD_MAKEVAR_PREFIX =
-#---------------------------------------------------------------------------
 # Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
 ENABLE_PREPROCESSING   = YES
index 6d6dbe5..f89fa62 100644 (file)
@@ -20,6 +20,8 @@
  * @defgroup Maintenance Maintenance
  */
 
+define( 'MW_ENTRY_POINT', 'cli' );
+
 // Bail on old versions of PHP, or if composer has not been run yet to install
 // dependencies.
 require_once __DIR__ . '/../includes/PHPVersionCheck.php';
diff --git a/maintenance/archives/patch-drop-user-fields.sql b/maintenance/archives/patch-drop-user-fields.sql
new file mode 100644 (file)
index 0000000..7faa593
--- /dev/null
@@ -0,0 +1,50 @@
+--
+-- patch-drop-user-fields.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+ALTER TABLE /*_*/archive
+  DROP INDEX /*i*/ar_usertext_timestamp,
+  DROP COLUMN ar_user,
+  DROP COLUMN ar_user_text,
+  ALTER COLUMN ar_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/ipblocks
+  DROP COLUMN ipb_by,
+  DROP COLUMN ipb_by_text,
+  ALTER COLUMN ipb_by_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/image
+  DROP INDEX /*i*/img_user_timestamp,
+  DROP INDEX /*i*/img_usertext_timestamp,
+  DROP COLUMN img_user,
+  DROP COLUMN img_user_text,
+  ALTER COLUMN img_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/oldimage
+  DROP INDEX /*i*/oi_usertext_timestamp,
+  DROP COLUMN oi_user,
+  DROP COLUMN oi_user_text,
+  ALTER COLUMN oi_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/filearchive
+  DROP INDEX /*i*/fa_user_timestamp,
+  DROP COLUMN fa_user,
+  DROP COLUMN fa_user_text,
+  ALTER COLUMN fa_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/recentchanges
+  DROP INDEX /*i*/rc_ns_usertext,
+  DROP INDEX /*i*/rc_user_text,
+  DROP COLUMN rc_user,
+  DROP COLUMN rc_user_text,
+  ALTER COLUMN rc_actor DROP DEFAULT;
+
+ALTER TABLE /*_*/logging
+  DROP INDEX /*i*/user_time,
+  DROP INDEX /*i*/log_user_type_time,
+  DROP INDEX /*i*/log_user_text_type_time,
+  DROP INDEX /*i*/log_user_text_time,
+  DROP COLUMN log_user,
+  DROP COLUMN log_user_text,
+  ALTER COLUMN log_actor DROP DEFAULT;
index 305a41d..6928181 100644 (file)
@@ -42,6 +42,9 @@ class CleanupImages extends TableCleanup {
                'callback' => 'processRow',
        ];
 
+       /** @var LocalRepo|null */
+       private $repo;
+
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Script to clean up broken, unparseable upload filenames' );
@@ -111,8 +114,12 @@ class CleanupImages extends TableCleanup {
                }
        }
 
+       /**
+        * @param string $name
+        * @return string
+        */
        private function filePath( $name ) {
-               if ( !isset( $this->repo ) ) {
+               if ( $this->repo === null ) {
                        $this->repo = RepoGroup::singleton()->getLocalRepo();
                }
 
index cad6122..5602e39 100644 (file)
@@ -133,7 +133,7 @@ class TitleCleanup extends TableCleanup {
         * @param Title $title
         */
        protected function moveInconsistentPage( $row, Title $title ) {
-               if ( $title->exists( Title::GAID_FOR_UPDATE )
+               if ( $title->exists( Title::READ_LATEST )
                        || $title->getInterwiki()
                        || !$title->canExist()
                ) {
index 3f55878..0d4b7b1 100644 (file)
@@ -39,6 +39,18 @@ require_once __DIR__ . '/dumpIterator.php';
 class CompareParsers extends DumpIterator {
 
        private $count = 0;
+       /** @var bool */
+       private $saveFailed;
+       /** @var bool */
+       private $stripParametersEnabled;
+       /** @var bool */
+       private $showParsedOutput;
+       /** @var bool */
+       private $showDiff;
+       /** @var ParserOptions */
+       private $options;
+       /** @var int */
+       private $failed;
 
        public function __construct() {
                parent::__construct();
index 23c46bc..26f8182 100644 (file)
@@ -100,10 +100,10 @@ class ConvertLinks extends Maintenance {
                # not used yet; highest row number from links table to process
                # $finalRowOffset = 0;
 
+               $this->logPerformance = $this->hasOption( 'logperformance' );
+               $perfLogFilename = $this->getArg( 1, "convLinksPerf.txt" );
                $overwriteLinksTable = !$this->hasOption( 'keep-links-table' );
                $noKeys = $this->hasOption( 'noKeys' );
-               $this->logPerformance = $this->hasOption( 'logperformance' );
-               $perfLogFilename = $this->getArg( 'perfLogFilename', "convLinksPerf.txt" );
 
                # --------------------------------------------------------------------
 
index 751932d..20e9459 100644 (file)
@@ -34,9 +34,10 @@ require_once __DIR__ . '/Maintenance.php';
  * @ingroup Maintenance
  */
 abstract class DumpIterator extends Maintenance {
-
        private $count = 0;
        private $startTime;
+       /** @var string|bool|null */
+       private $from;
 
        public function __construct() {
                parent::__construct();
@@ -60,6 +61,7 @@ abstract class DumpIterator extends Maintenance {
                        $revision->setTitle( Title::newFromText(
                                rawurldecode( basename( $this->getOption( 'file' ), '.txt' ) )
                        ) );
+                       $this->from = false;
                        $this->handleRevision( $revision );
 
                        return;
@@ -131,7 +133,7 @@ abstract class DumpIterator extends Maintenance {
                }
 
                $this->count++;
-               if ( isset( $this->from ) ) {
+               if ( $this->from !== false ) {
                        if ( $this->from != $title ) {
                                return;
                        }
index c4ca056..40cac1b 100644 (file)
@@ -32,6 +32,9 @@ require_once __DIR__ . '/Maintenance.php';
  * @ingroup Maintenance
  */
 class DumpUploads extends Maintenance {
+       /** @var string */
+       private $mBasePath;
+
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Generates list of uploaded files which can be fed to tar or similar.
@@ -44,30 +47,29 @@ By default, outputs relative paths against the parent directory of $wgUploadDire
 
        public function execute() {
                global $IP;
-               $this->mAction = 'fetchLocal';
                $this->mBasePath = $this->getOption( 'base', $IP );
-               $this->mShared = false;
-               $this->mSharedSupplement = false;
-
-               if ( $this->hasOption( 'local' ) ) {
-                       $this->mAction = 'fetchLocal';
-               }
-
-               if ( $this->hasOption( 'used' ) ) {
-                       $this->mAction = 'fetchUsed';
-               }
+               $shared = false;
+               $sharedSupplement = false;
 
                if ( $this->hasOption( 'shared' ) ) {
                        if ( $this->hasOption( 'used' ) ) {
                                // Include shared-repo files in the used check
-                               $this->mShared = true;
+                               $shared = true;
                        } else {
                                // Grab all local *plus* used shared
-                               $this->mSharedSupplement = true;
+                               $sharedSupplement = true;
                        }
                }
-               $this->{$this->mAction} ( $this->mShared );
-               if ( $this->mSharedSupplement ) {
+
+               if ( $this->hasOption( 'local' ) ) {
+                       $this->fetchLocal( $shared );
+               } elseif ( $this->hasOption( 'used' ) ) {
+                       $this->fetchUsed( $shared );
+               } else {
+                       $this->fetchLocal( $shared );
+               }
+
+               if ( $sharedSupplement ) {
                        $this->fetchUsed( true );
                }
        }
index d861348..7e9104c 100644 (file)
@@ -38,9 +38,7 @@ class GetReplicaServer extends Maintenance {
        }
 
        public function execute() {
-               if ( $this->getConfig()->get( 'AllDBsAreLocalhost' ) ) {
-                       $host = 'localhost';
-               } elseif ( $this->hasOption( 'group' ) ) {
+               if ( $this->hasOption( 'group' ) ) {
                        $db = $this->getDB( DB_REPLICA, $this->getOption( 'group' ) );
                        $host = $db->getServer();
                } else {
index cda16fe..d5f94ad 100644 (file)
@@ -43,6 +43,16 @@ class BackupReader extends Maintenance {
        public $imageBasePath = false;
        /** @var array|false */
        public $nsFilter = false;
+       /** @var bool|resource */
+       public $stderr;
+       /** @var callable|null */
+       protected $importCallback;
+       /** @var callable|null */
+       protected $logItemCallback;
+       /** @var callable|null */
+       protected $uploadCallback;
+       /** @var int */
+       protected $startTime;
 
        function __construct() {
                parent::__construct();
index 4065978..954f36d 100644 (file)
  * @author Mij <mij@bitchx.it>
  */
 
-use MediaWiki\MediaWikiServices;
-
 require_once __DIR__ . '/Maintenance.php';
 
+use MediaWiki\MediaWikiServices;
+
 class ImportImages extends Maintenance {
 
        public function __construct() {
@@ -127,6 +127,8 @@ class ImportImages extends Maintenance {
        public function execute() {
                global $wgFileExtensions, $wgUser, $wgRestrictionLevels;
 
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+
                $processed = $added = $ignored = $skipped = $overwritten = $failed = 0;
 
                $this->output( "Importing Files\n\n" );
@@ -198,7 +200,8 @@ class ImportImages extends Maintenance {
                                $title = Title::makeTitleSafe( NS_FILE, $base );
                                if ( !is_object( $title ) ) {
                                        $this->output(
-                                               "{$base} could not be imported; a valid title cannot be produced\n" );
+                                               "{$base} could not be imported; a valid title cannot be produced\n"
+                                       );
                                        continue;
                                }
 
@@ -213,10 +216,12 @@ class ImportImages extends Maintenance {
 
                                if ( $checkUserBlock && ( ( $processed % $checkUserBlock ) == 0 ) ) {
                                        $user->clearInstanceCache( 'name' ); // reload from DB!
-                                       // @TODO Use PermissionManager::isBlockedFrom() instead.
-                                       if ( $user->getBlock() ) {
-                                               $this->output( $user->getName() . " was blocked! Aborting.\n" );
-                                               break;
+                                       if ( $permissionManager->isBlockedFrom( $user, $title ) ) {
+                                               $this->output(
+                                                       "{$user->getName()} is blocked from {$title->getPrefixedText()}! skipping.\n"
+                                               );
+                                               $skipped++;
+                                               continue;
                                        }
                                }
 
@@ -242,7 +247,8 @@ class ImportImages extends Maintenance {
 
                                                if ( $dupes ) {
                                                        $this->output(
-                                                               "{$base} already exists as {$dupes[0]->getName()}, skipping\n" );
+                                                               "{$base} already exists as {$dupes[0]->getName()}, skipping\n"
+                                                       );
                                                        $skipped++;
                                                        continue;
                                                }
@@ -270,7 +276,8 @@ class ImportImages extends Maintenance {
                                                if ( $wgUser === false ) {
                                                        # user does not exist in target wiki
                                                        $this->output(
-                                                               "failed: user '$real_user' does not exist in target wiki." );
+                                                               "failed: user '$real_user' does not exist in target wiki."
+                                                       );
                                                        continue;
                                                }
                                        }
@@ -287,7 +294,8 @@ class ImportImages extends Maintenance {
                                                        $commentText = file_get_contents( $f );
                                                        if ( !$commentText ) {
                                                                $this->output(
-                                                                       " Failed to load comment file {$f}, using default comment. " );
+                                                                       " Failed to load comment file {$f}, using default comment. "
+                                                               );
                                                        }
                                                }
                                        }
index 358dc21..6c1c083 100644 (file)
@@ -49,6 +49,8 @@ abstract class BackupDumper extends Maintenance {
        public $dumpUploadFileContents = false;
        public $orderRevs = false;
        public $limitNamespaces = [];
+       /** @var bool|resource */
+       public $stderr;
 
        protected $reportingInterval = 100;
        protected $pageCount = 0;
@@ -65,6 +67,33 @@ abstract class BackupDumper extends Maintenance {
 
        protected $ID = 0;
 
+       /** @var int */
+       protected $startTime;
+       /** @var int */
+       protected $pageCountPart;
+       /** @var int */
+       protected $revCountPart;
+       /** @var int */
+       protected $maxCount;
+       /** @var int */
+       protected $timeOfCheckpoint;
+       /** @var ExportProgressFilter */
+       protected $egress;
+       /** @var string */
+       protected $buffer;
+       /** @var array|false */
+       protected $openElement;
+       /** @var bool */
+       protected $atStart;
+       /** @var string|null */
+       protected $thisRevModel;
+       /** @var string|null */
+       protected $thisRevFormat;
+       /** @var string */
+       protected $lastName;
+       /** @var string */
+       protected $state;
+
        /**
         * The dependency-injected database to use.
         *
diff --git a/maintenance/includes/MWDoxygenFilter.php b/maintenance/includes/MWDoxygenFilter.php
new file mode 100644 (file)
index 0000000..287a927
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Copyright (C) 2012 Tamas Imrei <tamas.imrei@gmail.com> https://virtualtee.blogspot.com/
+ *
+ * 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 Maintenance
+ * @phan-file-suppress PhanInvalidCommentForDeclarationType False negative about about `@var`
+ * @phan-file-suppress PhanUnextractableAnnotation False negative about about `@var`
+ */
+
+/**
+ * Doxygen filter to show correct member variable types in documentation.
+ *
+ * Based on
+ * <https://virtualtee.blogspot.co.uk/2012/03/tip-for-using-doxygen-for-php-code.html>
+ *
+ * It has been adapted for MediaWiki to resolve various bugs we experienced
+ * from using Doxygen with our coding conventions:
+ *
+ * - We want to allow documenting class members on a single line by documenting
+ *   them as `/** @var SomeType Description here.`, and in long-form as
+ *   `/**\n * Description here.\n * @var SomeType`.
+ *
+ * - PHP does not support native type-hinting of class members. Instead, we document
+ *   that using `@var` in the doc blocks above it. However, Doxygen only supports
+ *   parsing this from executable code. We achieve this by having the below filter
+ *   take the typehint from the doc block and insert it into the source code in
+ *   front of `$myvar`, like `protected SomeType $myvar`. This result is technically
+ *   invalid PHP code, but Doxygen understands it this way.
+ *
+ * @internal For use by maintenance/mwdoc-filter.php
+ * @ingroup Maintenance
+ */
+class MWDoxygenFilter {
+       /**
+        * @param string $source Original source code
+        * @return string Filtered source code
+        */
+       public static function filter( $source ) {
+               $tokens = token_get_all( $source );
+               $buffer = null;
+               $output = '';
+               foreach ( $tokens as $token ) {
+                       if ( is_string( $token ) ) {
+                               if ( $buffer !== null && $token === ';' ) {
+                                       // If we still have a buffer and the statement has ended,
+                                       // flush it and move on.
+                                       $output .= $buffer['raw'];
+                                       $buffer = null;
+                               }
+                               $output .= $token;
+                               continue;
+                       }
+                       list( $id, $content ) = $token;
+                       switch ( $id ) {
+                               case T_DOC_COMMENT:
+                                       // Escape slashes so that references to namespaces are not
+                                       // wrongly interpreted as a Doxygen "\command".
+                                       $content = addcslashes( $content, '\\' );
+                                       // Look for instances of "@var SomeType".
+                                       if ( preg_match( '#@var\s+\S+#s', $content ) ) {
+                                               $buffer = [ 'raw' => $content, 'desc' => null, 'type' => null, 'name' => null ];
+                                               $buffer['desc'] = preg_replace_callback(
+                                                       // Strip "@var SomeType" part, but remember the type and optional name
+                                                       '#@var\s+(\S+)(\s+)?(\S+)?#s',
+                                                       function ( $matches ) use ( &$buffer ) {
+                                                               $buffer['type'] = $matches[1];
+                                                               $buffer['name'] = $matches[3] ?? null;
+                                                               return ( $matches[2] ?? '' ) . ( $matches[3] ?? '' );
+                                                       },
+                                                       $content
+                                               );
+                                       } else {
+                                               $output .= $content;
+                                       }
+                                       break;
+
+                               case T_VARIABLE:
+                                       // Doxygen requires class members to be documented in one of two ways:
+                                       //
+                                       // 1. Fully qualified:
+                                       //    /** @var SomeType $name Description here. */
+                                       //
+                                       //    These result in the creation of a new virtual node called $name
+                                       //    with the specified type and description. The real code doesn't
+                                       //    even need to exist in this case.
+                                       //
+                                       // 2. Contextual:
+                                       //    /** Description here. */
+                                       //    private SomeType? $name;
+                                       //
+                                       // In MediaWiki, we are mostly like #1 but without the name repeated:
+                                       //    /** @var SomeType Description here. */
+                                       //    private $name;
+                                       //
+                                       // These emit a warning in Doxygen because they are missing a variable name.
+                                       // Convert these to the "Contextual" kind by stripping ""@var", injecting
+                                       // type into the code, and leaving the description in-place.
+                                       if ( $buffer !== null ) {
+                                               if ( $buffer['name'] === $content ) {
+                                                       // Fully qualitied "@var" comment, leave as-is.
+                                                       $output .= $buffer['raw'];
+                                                       $output .= $content;
+                                               } else {
+                                                       // MW-style "@var" comment. Keep only the description and transplant
+                                                       // the type into the code.
+                                                       $output .= $buffer['desc'];
+                                                       $output .= "{$buffer['type']} $content";
+                                               }
+                                               $buffer = null;
+                                       } else {
+                                               $output .= $content;
+                                       }
+                                       break;
+
+                               default:
+                                       if ( $buffer !== null ) {
+                                               $buffer['raw'] .= $content;
+                                               $buffer['desc'] .= $content;
+                                       } else {
+                                               $output .= $content;
+                                       }
+                                       break;
+                       }
+               }
+               return $output;
+       }
+}
index 1b35a20..48dcf8d 100644 (file)
@@ -51,15 +51,6 @@ class MigrateActors extends LoggedUpdateMaintenance {
        }
 
        protected function doDBUpdates() {
-               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
-               if ( !( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
-                       $this->output(
-                               "...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
-                       );
-                       return false;
-               }
-
                $tables = $this->getOption( 'tables' );
                if ( $tables !== null ) {
                        $this->tables = explode( ',', $tables );
@@ -265,6 +256,12 @@ class MigrateActors extends LoggedUpdateMaintenance {
                        return 0;
                }
 
+               $dbw = $this->getDB( DB_MASTER );
+               if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
+                       $this->output( "No need to migrate $table.$userField, field does not exist\n" );
+                       return 0;
+               }
+
                $complainedAboutUsers = [];
 
                $primaryKey = (array)$primaryKey;
@@ -274,7 +271,6 @@ class MigrateActors extends LoggedUpdateMaintenance {
                );
                wfWaitForSlaves();
 
-               $dbw = $this->getDB( DB_MASTER );
                $actorIdSubquery = $this->makeActorIdSubquery( $dbw, $userField, $nameField );
                $next = '1=1';
                $countUpdated = 0;
@@ -357,6 +353,7 @@ class MigrateActors extends LoggedUpdateMaintenance {
         * @param string $nameField User name field name
         * @param string $newPrimaryKey Primary key of the new table.
         * @param string $actorField Actor field name
+        * @return int Number of errors
         */
        protected function migrateToTemp(
                $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
@@ -366,6 +363,12 @@ class MigrateActors extends LoggedUpdateMaintenance {
                        return 0;
                }
 
+               $dbw = $this->getDB( DB_MASTER );
+               if ( !$dbw->fieldExists( $table, $userField, __METHOD__ ) ) {
+                       $this->output( "No need to migrate $table.$userField, field does not exist\n" );
+                       return 0;
+               }
+
                $complainedAboutUsers = [];
 
                $newTable = $table . '_actor_temp';
@@ -374,7 +377,6 @@ class MigrateActors extends LoggedUpdateMaintenance {
                );
                wfWaitForSlaves();
 
-               $dbw = $this->getDB( DB_MASTER );
                $actorIdSubquery = $this->makeActorIdSubquery( $dbw, $userField, $nameField );
                $next = [];
                $countUpdated = 0;
index a71bb74..16b8ccf 100644 (file)
@@ -118,7 +118,7 @@ class CommandLineInstaller extends Maintenance {
                try {
                        $installer = InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions );
                } catch ( \MediaWiki\Installer\InstallException $e ) {
-                       $this->output( $e->getStatus()->getMessage()->parse() . "\n" );
+                       $this->output( $e->getStatus()->getMessage()->text() . "\n" );
                        return false;
                }
 
@@ -133,6 +133,8 @@ class CommandLineInstaller extends Maintenance {
                if ( !$envChecksOnly ) {
                        $status = $installer->execute();
                        if ( !$status->isGood() ) {
+                               $installer->showStatusMessage( $status );
+
                                return false;
                        }
                        $installer->writeConfigurationFile( $this->getOption( 'confpath', $IP ) );
index 54c1d38..b19420f 100644 (file)
@@ -21,7 +21,6 @@
                                "classes": [
                                        "mw.Title",
                                        "mw.Uri",
-                                       "mw.RegExp",
                                        "mw.String",
                                        "mw.messagePoster.*",
                                        "mw.notification",
index 48a6666..d78c5a0 100644 (file)
@@ -129,7 +129,6 @@ class MergeMessageFileList extends Maintenance {
                $files = [];
                $fileLines = file( $fileName );
                if ( $fileLines === false ) {
-                       $this->hasError = true;
                        $this->error( "Unable to open list file $fileName." );
 
                        return $files;
@@ -144,7 +143,6 @@ class MergeMessageFileList extends Maintenance {
                                if ( file_exists( $extension ) ) {
                                        $files[] = $extension;
                                } else {
-                                       $this->hasError = true;
                                        $this->error( "Extension {$extension} doesn't exist" );
                                }
                        }
index 1da805e..cabf489 100644 (file)
@@ -1,22 +1,9 @@
 <?php
 /**
- * Doxygen filter to show correct member variable types in documentation.
+ * Filter for PHP source code that allows Doxygen to understand it better.
  *
- * Should be set in Doxygen INPUT_FILTER as "php mwdoc-filter.php"
- *
- * Based on
- * <https://virtualtee.blogspot.co.uk/2012/03/tip-for-using-doxygen-for-php-code.html>
- *
- * Improved to resolve various bugs and better MediaWiki PHPDoc conventions:
- *
- * - Insert variable name after typehint instead of at end of line so that
- *   documentation text may follow after "@var Type".
- * - Insert typehint into source code before $variable instead of inside the comment
- *   so that Doxygen interprets it.
- * - Strip the text after @var from the output to avoid Doxygen warnings aboug bogus
- *   symbols being documented but not declared or defined.
- *
- * Copyright (C) 2012 Tamas Imrei <tamas.imrei@gmail.com> https://virtualtee.blogspot.com/
+ * This CLI script is intended to be configured as the INPUT_FILTER
+ * script in a Doxyfile, e.g. like "php mwdoc-filter.php".
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files (the "Software"),
@@ -42,59 +29,7 @@ if ( PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg' ) {
        die( "This filter can only be run from the command line.\n" );
 }
 
-$source = file_get_contents( $argv[1] );
-$tokens = token_get_all( $source );
+require_once __DIR__ . '/includes/MWDoxygenFilter.php';
 
-$buffer = $bufferType = null;
-foreach ( $tokens as $token ) {
-       if ( is_string( $token ) ) {
-               if ( $buffer !== null && $token === ';' ) {
-                       // If we still have a buffer and the statement has ended,
-                       // flush it and move on.
-                       echo $buffer;
-                       $buffer = $bufferType = null;
-               }
-               echo $token;
-               continue;
-       }
-       list( $id, $content ) = $token;
-       switch ( $id ) {
-               case T_DOC_COMMENT:
-                       // Escape slashes so that references to namespaces are not
-                       // wrongly interpreted as a Doxygen "\command".
-                       $content = addcslashes( $content, '\\' );
-                       // Look for instances of "@var Type" not followed by $name.
-                       if ( preg_match( '#@var\s+([^\s]+)\s+([^\$]+)#s', $content ) ) {
-                               $buffer = preg_replace_callback(
-                                       // Strip the "@var Type" part and remember the type
-                                       '#(@var\s+)([^\s]+)#s',
-                                       function ( $matches ) use ( &$bufferType ) {
-                                               $bufferType = $matches[2];
-                                               return '';
-                                       },
-                                       $content
-                               );
-                       } else {
-                               echo $content;
-                       }
-                       break;
-
-               case T_VARIABLE:
-                       if ( $buffer !== null ) {
-                               echo $buffer;
-                               echo "$bufferType $content";
-                               $buffer = $bufferType = null;
-                       } else {
-                               echo $content;
-                       }
-                       break;
-
-               default:
-                       if ( $buffer !== null ) {
-                               $buffer .= $content;
-                       } else {
-                               echo $content;
-                       }
-                       break;
-       }
-}
+$source = file_get_contents( $argv[1] );
+echo MWDoxygenFilter::filter( $source );
index 791b360..f600f13 100644 (file)
@@ -42,6 +42,26 @@ require_once __DIR__ . '/Maintenance.php';
  * @ingroup Maintenance
  */
 class MWDocGen extends Maintenance {
+       /** @var string */
+       private $doxygen;
+       /** @var string */
+       private $mwVersion;
+       /** @var string */
+       private $output;
+       /** @var string */
+       private $input;
+       /** @var string */
+       private $inputFilter;
+       /** @var string */
+       private $template;
+       /** @var string[] */
+       private $excludes;
+       /** @var string[] */
+       private $excludePatterns;
+       /** @var bool */
+       private $doDot;
+       /** @var bool */
+       private $doMan;
 
        /**
         * Prepare Maintenance class
@@ -56,8 +76,6 @@ class MWDocGen extends Maintenance {
                $this->addOption( 'version',
                        'Pass a MediaWiki version',
                        false, true );
-               $this->addOption( 'generate-man',
-                       'Whether to generate man files' );
                $this->addOption( 'file',
                        "Only process given file or directory. Multiple values " .
                        "accepted with comma separation. Path relative to \$IP.",
@@ -99,6 +117,7 @@ class MWDocGen extends Maintenance {
                $this->excludes = [
                        'vendor',
                        'node_modules',
+                       'resources/lib',
                        'images',
                        'static',
                ];
@@ -108,7 +127,6 @@ class MWDocGen extends Maintenance {
                }
 
                $this->doDot = shell_exec( 'which dot' );
-               $this->doMan = $this->hasOption( 'generate-man' );
        }
 
        public function execute() {
@@ -133,7 +151,6 @@ class MWDocGen extends Maintenance {
                                '{{EXCLUDE}}' => $exclude,
                                '{{EXCLUDE_PATTERNS}}' => $excludePatterns,
                                '{{HAVE_DOT}}' => $this->doDot ? 'YES' : 'NO',
-                               '{{GENERATE_MAN}}' => $this->doMan ? 'YES' : 'NO',
                                '{{INPUT_FILTER}}' => $this->inputFilter,
                        ]
                );
index 87fb396..20096a2 100644 (file)
@@ -244,9 +244,7 @@ CREATE TABLE archive (
   ar_parent_id      INTEGER          NULL,
   ar_sha1           TEXT         NOT NULL DEFAULT '',
   ar_comment_id     INTEGER      NOT NULL,
-  ar_user           INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  ar_user_text      TEXT         NOT NULL DEFAULT '',
-  ar_actor          INTEGER      NOT NULL DEFAULT 0,
+  ar_actor          INTEGER      NOT NULL,
   ar_timestamp      TIMESTAMPTZ  NOT NULL,
   ar_minor_edit     SMALLINT     NOT NULL  DEFAULT 0,
   ar_rev_id         INTEGER      NOT NULL,
@@ -258,7 +256,6 @@ CREATE TABLE archive (
 );
 ALTER SEQUENCE archive_ar_id_seq OWNED BY archive.ar_id;
 CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
-CREATE INDEX archive_user_text            ON archive (ar_user_text);
 CREATE INDEX archive_actor                ON archive (ar_actor);
 CREATE UNIQUE INDEX ar_revid_uniq ON archive (ar_rev_id);
 
@@ -404,9 +401,7 @@ CREATE TABLE ipblocks (
   ipb_id                INTEGER      NOT NULL  PRIMARY KEY DEFAULT nextval('ipblocks_ipb_id_seq'),
   ipb_address           TEXT             NULL,
   ipb_user              INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  ipb_by                INTEGER      NOT NULL  DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
-  ipb_by_text           TEXT         NOT NULL  DEFAULT '',
-  ipb_by_actor          INTEGER      NOT NULL  DEFAULT 0,
+  ipb_by_actor          INTEGER      NOT NULL,
   ipb_reason_id         INTEGER      NOT NULL,
   ipb_timestamp         TIMESTAMPTZ  NOT NULL,
   ipb_auto              SMALLINT     NOT NULL  DEFAULT 0,
@@ -448,9 +443,7 @@ CREATE TABLE image (
   img_major_mime   TEXT                DEFAULT 'unknown',
   img_minor_mime   TEXT                DEFAULT 'unknown',
   img_description_id INTEGER NOT NULL,
-  img_user         INTEGER   NOT NULL  DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  img_user_text    TEXT      NOT NULL  DEFAULT '',
-  img_actor        INTEGER   NOT NULL  DEFAULT 0,
+  img_actor        INTEGER   NOT NULL,
   img_timestamp    TIMESTAMPTZ,
   img_sha1         TEXT      NOT NULL  DEFAULT ''
 );
@@ -466,9 +459,7 @@ CREATE TABLE oldimage (
   oi_height        INTEGER      NOT NULL,
   oi_bits          SMALLINT         NULL,
   oi_description_id INTEGER     NOT NULL,
-  oi_user          INTEGER      NOT NULL DEFAULT 0  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  oi_user_text     TEXT         NOT NULL DEFAULT '',
-  oi_actor         INTEGER      NOT NULL DEFAULT 0,
+  oi_actor         INTEGER      NOT NULL,
   oi_timestamp     TIMESTAMPTZ      NULL,
   oi_metadata      BYTEA        NOT NULL DEFAULT '',
   oi_media_type    TEXT             NULL,
@@ -502,9 +493,7 @@ CREATE TABLE filearchive (
   fa_major_mime         TEXT                   DEFAULT 'unknown',
   fa_minor_mime         TEXT                   DEFAULT 'unknown',
   fa_description_id     INTEGER      NOT NULL,
-  fa_user               INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  fa_user_text          TEXT         NOT NULL DEFAULT '',
-  fa_actor              INTEGER      NOT NULL DEFAULT 0,
+  fa_actor              INTEGER      NOT NULL,
   fa_timestamp          TIMESTAMPTZ,
   fa_deleted            SMALLINT     NOT NULL DEFAULT 0,
   fa_sha1               TEXT         NOT NULL DEFAULT ''
@@ -548,9 +537,7 @@ CREATE SEQUENCE recentchanges_rc_id_seq;
 CREATE TABLE recentchanges (
   rc_id              INTEGER      NOT NULL  PRIMARY KEY DEFAULT nextval('recentchanges_rc_id_seq'),
   rc_timestamp       TIMESTAMPTZ  NOT NULL,
-  rc_user            INTEGER      NOT NULL  DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  rc_user_text       TEXT         NOT NULL  DEFAULT '',
-  rc_actor           INTEGER      NOT NULL  DEFAULT 0,
+  rc_actor           INTEGER      NOT NULL,
   rc_namespace       SMALLINT     NOT NULL,
   rc_title           TEXT         NOT NULL,
   rc_comment_id      INTEGER      NOT NULL,
@@ -646,27 +633,21 @@ CREATE TABLE logging (
   log_type        TEXT         NOT NULL,
   log_action      TEXT         NOT NULL,
   log_timestamp   TIMESTAMPTZ  NOT NULL,
-  log_user        INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  log_actor       INTEGER      NOT NULL DEFAULT 0,
+  log_actor       INTEGER      NOT NULL,
   log_namespace   SMALLINT     NOT NULL,
   log_title       TEXT         NOT NULL,
   log_comment_id  INTEGER      NOT NULL,
   log_params      TEXT,
   log_deleted     SMALLINT     NOT NULL DEFAULT 0,
-  log_user_text   TEXT         NOT NULL DEFAULT '',
   log_page        INTEGER
 );
 ALTER SEQUENCE logging_log_id_seq OWNED BY logging.log_id;
 CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
-CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
 CREATE INDEX logging_actor_time_backwards ON logging (log_timestamp, log_actor);
 CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
 CREATE INDEX logging_times ON logging (log_timestamp);
-CREATE INDEX logging_user_type_time ON logging (log_user, log_type, log_timestamp);
 CREATE INDEX logging_actor_type_time ON logging (log_actor, log_type, log_timestamp);
 CREATE INDEX logging_page_id_time ON logging (log_page, log_timestamp);
-CREATE INDEX logging_user_text_type_time ON logging (log_user_text, log_type, log_timestamp);
-CREATE INDEX logging_user_text_time ON logging (log_user_text, log_timestamp);
 CREATE INDEX logging_actor_time ON logging (log_actor, log_timestamp);
 CREATE INDEX logging_type_action ON logging (log_type, log_action, log_timestamp);
 
index 963bfec..fddac8a 100644 (file)
@@ -41,6 +41,8 @@ class PreprocessDump extends DumpIterator {
        /* Variables for dressing up as a parser */
        public $mTitle = 'PreprocessDump';
        public $mPPNodeCount = 0;
+       /** @var Preprocessor */
+       public $mPreprocessor;
 
        public function getStripList() {
                $parser = MediaWikiServices::getInstance()->getParser();
@@ -73,8 +75,9 @@ class PreprocessDump extends DumpIterator {
                        $name = Preprocessor_DOM::class;
                }
 
-               MediaWikiServices::getInstance()->getParser()->firstCallInit();
-               $this->mPreprocessor = new $name( $this );
+               $parser = MediaWikiServices::getInstance()->getParser();
+               $parser->firstCallInit();
+               $this->mPreprocessor = new $name( $parser );
        }
 
        /**
index 68fb643..3a43ae8 100644 (file)
@@ -150,6 +150,16 @@ class PPFuzzTester {
 class PPFuzzTest {
        public $templates, $mainText, $title, $entryPoint, $output;
 
+       /** @var PPFuzzTester */
+       private $parent;
+       /** @var string */
+       public $nickname;
+       /** @var bool */
+       public $fancySig;
+
+       /**
+        * @param PPFuzzTester $tester
+        */
        function __construct( $tester ) {
                global $wgMaxSigChars;
                $this->parent = $tester;
index 54f1862..f3b856c 100644 (file)
@@ -23,8 +23,6 @@
  * @license GPL-2.0-or-later
  */
 
-use Wikimedia\Rdbms\IDatabase;
-
 require_once __DIR__ . '/Maintenance.php';
 
 /**
@@ -76,8 +74,6 @@ class ReassignEdits extends Maintenance {
         * @return int Number of entries changed, or that would be changed
         */
        private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
-               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
                $dbw = $this->getDB( DB_MASTER );
                $this->beginTransaction( $dbw, __METHOD__ );
 
@@ -136,36 +132,22 @@ class ReassignEdits extends Maintenance {
                        if ( $total ) {
                                # Reassign edits
                                $this->output( "\nReassigning current edits..." );
-                               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                                       $dbw->update(
-                                               'revision',
-                                               [
-                                                       'rev_user' => $to->getId(),
-                                                       'rev_user_text' => $to->getName(),
-                                               ],
-                                               $from->isLoggedIn()
-                                                       ? [ 'rev_user' => $from->getId() ] : [ 'rev_user_text' => $from->getName() ],
-                                               __METHOD__
-                                       );
-                               }
-                               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                                       $dbw->update(
-                                               'revision_actor_temp',
-                                               [ 'revactor_actor' => $to->getActorId( $dbw ) ],
-                                               [ 'revactor_actor' => $from->getActorId() ],
-                                               __METHOD__
-                                       );
-                               }
+                               $dbw->update(
+                                       'revision_actor_temp',
+                                       [ 'revactor_actor' => $to->getActorId( $dbw ) ],
+                                       [ 'revactor_actor' => $from->getActorId() ],
+                                       __METHOD__
+                               );
                                $this->output( "done.\nReassigning deleted edits..." );
                                $dbw->update( 'archive',
-                                       $this->userSpecification( $dbw, $to, 'ar_user', 'ar_user_text', 'ar_actor' ),
+                                       [ 'ar_actor' => $to->getActorId( $dbw ) ],
                                        [ $arQueryInfo['conds'] ], __METHOD__ );
                                $this->output( "done.\n" );
                                # Update recent changes if required
                                if ( $rc ) {
                                        $this->output( "Updating recent changes..." );
                                        $dbw->update( 'recentchanges',
-                                               $this->userSpecification( $dbw, $to, 'rc_user', 'rc_user_text', 'rc_actor' ),
+                                               [ 'rc_actor' => $to->getActorId( $dbw ) ],
                                                [ $rcQueryInfo['conds'] ], __METHOD__ );
                                        $this->output( "done.\n" );
                                }
@@ -177,33 +159,6 @@ class ReassignEdits extends Maintenance {
                return (int)$total;
        }
 
-       /**
-        * Return user specifications for an UPDATE
-        * i.e. user => id, user_text => text
-        *
-        * @param IDatabase $dbw Database handle
-        * @param User $user User for the spec
-        * @param string $idfield Field name containing the identifier
-        * @param string $utfield Field name containing the user text
-        * @param string $acfield Field name containing the actor ID
-        * @return array
-        */
-       private function userSpecification( IDatabase $dbw, &$user, $idfield, $utfield, $acfield ) {
-               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
-               $ret = [];
-               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                       $ret += [
-                               $idfield => $user->getId(),
-                               $utfield => $user->getName(),
-                       ];
-               }
-               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $ret += [ $acfield => $user->getActorId( $dbw ) ];
-               }
-               return $ret;
-       }
-
        /**
         * Initialise the user object
         *
index bfbee9b..df2ab04 100644 (file)
@@ -41,12 +41,32 @@ use Wikimedia\Rdbms\IMaintainableDatabase;
  * @ingroup Maintenance
  */
 class ImageBuilder extends Maintenance {
-
        /**
         * @var IMaintainableDatabase
         */
        protected $dbw;
 
+       /** @var bool */
+       private $dryrun;
+
+       /** @var LocalRepo|null */
+       private $repo;
+
+       /** @var int */
+       private $updated;
+
+       /** @var int */
+       private $processed;
+
+       /** @var int */
+       private $count;
+
+       /** @var int */
+       private $startTime;
+
+       /** @var string */
+       private $table;
+
        function __construct() {
                parent::__construct();
 
@@ -79,7 +99,7 @@ class ImageBuilder extends Maintenance {
         * @return LocalRepo
         */
        function getRepo() {
-               if ( !isset( $this->repo ) ) {
+               if ( $this->repo === null ) {
                        $this->repo = RepoGroup::singleton()->getLocalRepo();
                }
 
@@ -91,6 +111,10 @@ class ImageBuilder extends Maintenance {
                $this->buildOldImage();
        }
 
+       /**
+        * @param int $count
+        * @param string $table
+        */
        function init( $count, $table ) {
                $this->processed = 0;
                $this->updated = 0;
index 7e8f063..8ec648f 100644 (file)
@@ -36,6 +36,12 @@ use MediaWiki\MediaWikiServices;
  * @ingroup Maintenance
  */
 class RecountCategories extends Maintenance {
+       /** @var string */
+       private $mode;
+
+       /** @var int */
+       private $minimumId;
+
        public function __construct() {
                parent::__construct();
                $this->addDescription( <<<'TEXT'
index 16a7346..ca90468 100644 (file)
@@ -39,8 +39,6 @@ class RemoveUnusedAccounts extends Maintenance {
        }
 
        public function execute() {
-               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
-
                $this->output( "Remove unused accounts\n\n" );
 
                # Do an initial scan for inactive accounts and report the result
@@ -48,18 +46,14 @@ class RemoveUnusedAccounts extends Maintenance {
                $delUser = [];
                $delActor = [];
                $dbr = $this->getDB( DB_REPLICA );
-               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $res = $dbr->select(
-                               [ 'user', 'actor' ],
-                               [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
-                               '',
-                               __METHOD__,
-                               [],
-                               [ 'actor' => [ 'LEFT JOIN', 'user_id = actor_user' ] ]
-                       );
-               } else {
-                       $res = $dbr->select( 'user', [ 'user_id', 'user_name', 'user_touched' ], '', __METHOD__ );
-               }
+               $res = $dbr->select(
+                       [ 'user', 'actor' ],
+                       [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
+                       '',
+                       __METHOD__,
+                       [],
+                       [ 'actor' => [ 'LEFT JOIN', 'user_id = actor_user' ] ]
+               );
                if ( $this->hasOption( 'ignore-groups' ) ) {
                        $excludedGroups = explode( ',', $this->getOption( 'ignore-groups' ) );
                } else {
@@ -94,30 +88,22 @@ class RemoveUnusedAccounts extends Maintenance {
                        $this->output( "\nDeleting unused accounts..." );
                        $dbw = $this->getDB( DB_MASTER );
                        $dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
-                       if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               # Keep actor rows referenced from ipblocks
-                               $keep = $dbw->selectFieldValues(
-                                       'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
-                               );
-                               $del = array_diff( $delActor, $keep );
-                               if ( $del ) {
-                                       $dbw->delete( 'actor', [ 'actor_id' => $del ], __METHOD__ );
-                               }
-                               if ( $keep ) {
-                                       $dbw->update( 'actor', [ 'actor_user' => 0 ], [ 'actor_id' => $keep ], __METHOD__ );
-                               }
+                       # Keep actor rows referenced from ipblocks
+                       $keep = $dbw->selectFieldValues(
+                               'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
+                       );
+                       $del = array_diff( $delActor, $keep );
+                       if ( $del ) {
+                               $dbw->delete( 'actor', [ 'actor_id' => $del ], __METHOD__ );
+                       }
+                       if ( $keep ) {
+                               $dbw->update( 'actor', [ 'actor_user' => 0 ], [ 'actor_id' => $keep ], __METHOD__ );
                        }
                        $dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
                        $dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
                        $dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
-                       if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
-                               $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
-                       }
-                       if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                               $dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
-                               $dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
-                       }
+                       $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
+                       $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
                        $this->output( "done.\n" );
                        # Update the site_stats.ss_users field
                        $users = $dbw->selectField( 'user', 'COUNT(*)', [], __METHOD__ );
index cc5ae59..85795ca 100644 (file)
@@ -40,6 +40,8 @@ class DumpRenderer extends Maintenance {
 
        private $count = 0;
        private $outputDirectory, $startTime;
+       /** @var string */
+       private $prefix;
 
        public function __construct() {
                parent::__construct();
index 284db2c..154482c 100644 (file)
@@ -50,10 +50,10 @@ class ResetUserTokens extends Maintenance {
        }
 
        public function execute() {
-               $this->nullsOnly = $this->getOption( 'nulls' );
+               $nullsOnly = $this->getOption( 'nulls' );
 
                if ( !$this->getOption( 'nowarn' ) ) {
-                       if ( $this->nullsOnly ) {
+                       if ( $nullsOnly ) {
                                $this->output( "The script is about to reset the user_token "
                                        . "for USERS WITH NULL TOKENS in the database.\n" );
                        } else {
@@ -71,7 +71,7 @@ class ResetUserTokens extends Maintenance {
                $dbr = $this->getDB( DB_REPLICA );
 
                $where = [];
-               if ( $this->nullsOnly ) {
+               if ( $nullsOnly ) {
                        // Have to build this by hand, because \ is escaped in helper functions
                        $where = [ 'user_token = \'' . str_repeat( '\0', 32 ) . '\'' ];
                }
diff --git a/maintenance/sqlite/archives/patch-archive-drop-ar_user.sql b/maintenance/sqlite/archives/patch-archive-drop-ar_user.sql
new file mode 100644 (file)
index 0000000..ceb7d7d
--- /dev/null
@@ -0,0 +1,44 @@
+--
+-- patch-archive-drop-ar_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+CREATE TABLE /*_*/archive_tmp (
+  ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_comment_id bigint unsigned NOT NULL,
+  ar_actor bigint unsigned NOT NULL,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_rev_id int unsigned NOT NULL,
+  ar_text_id int unsigned NOT NULL DEFAULT 0,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL,
+  ar_sha1 varbinary(32) NOT NULL default '',
+  ar_content_model varbinary(32) DEFAULT NULL,
+  ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/archive_tmp (
+       ar_id, ar_namespace, ar_title, ar_comment_id, ar_actor,
+       ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted,
+       ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format
+  ) SELECT
+       ar_id, ar_namespace, ar_title, ar_comment_id, ar_actor,
+       ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted,
+       ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format
+  FROM /*_*/archive;
+
+DROP TABLE /*_*/archive;
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
+CREATE UNIQUE INDEX /*i*/ar_revid_uniq ON /*_*/archive (ar_rev_id);
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-filearchive-drop-fa_user.sql b/maintenance/sqlite/archives/patch-filearchive-drop-fa_user.sql
new file mode 100644 (file)
index 0000000..0a20a56
--- /dev/null
@@ -0,0 +1,55 @@
+--
+-- patch-filearchive-drop-fa_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/filearchive_tmp;
+CREATE TABLE /*_*/filearchive_tmp (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason_id bigint unsigned NOT NULL,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
+  fa_minor_mime varbinary(100) default "unknown",
+  fa_description_id bigint unsigned NOT NULL,
+  fa_actor bigint unsigned NOT NULL DEFAULT 0,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0,
+  fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/filearchive_tmp (
+       fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+       fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason_id,
+       fa_size, fa_width, fa_height, fa_metadata, fa_bits,
+       fa_media_type, fa_major_mime, fa_minor_mime, fa_description_id,
+       fa_actor, fa_timestamp, fa_deleted, fa_sha1
+  ) SELECT
+       fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+       fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason_id,
+       fa_size, fa_width, fa_height, fa_metadata, fa_bits,
+       fa_media_type, fa_major_mime, fa_minor_mime, fa_description_id,
+       fa_actor, fa_timestamp, fa_deleted, fa_sha1
+  FROM /*_*/filearchive;
+
+DROP TABLE /*_*/filearchive;
+ALTER TABLE /*_*/filearchive_tmp RENAME TO /*_*/filearchive;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-image-drop-img_user.sql b/maintenance/sqlite/archives/patch-image-drop-img_user.sql
new file mode 100644 (file)
index 0000000..d93e122
--- /dev/null
@@ -0,0 +1,43 @@
+--
+-- patch-image-drop-img_description.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/image_tmp;
+CREATE TABLE /*_*/image_tmp (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+  img_minor_mime varbinary(100) NOT NULL default "unknown",
+  img_description_id bigint unsigned NOT NULL,
+  img_actor bigint unsigned NOT NULL,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/image_tmp (
+       img_name, img_size, img_width, img_height, img_metadata, img_bits,
+       img_media_type, img_major_mime, img_minor_mime, img_description_id,
+       img_actor, img_timestamp, img_sha1
+  ) SELECT
+       img_name, img_size, img_width, img_height, img_metadata, img_bits,
+       img_media_type, img_major_mime, img_minor_mime, img_description_id,
+       img_actor, img_timestamp, img_sha1
+  FROM /*_*/image;
+
+DROP TABLE /*_*/image;
+ALTER TABLE /*_*/image_tmp RENAME TO /*_*/image;
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-ipblocks-drop-ipb_by.sql b/maintenance/sqlite/archives/patch-ipblocks-drop-ipb_by.sql
new file mode 100644 (file)
index 0000000..2de5e09
--- /dev/null
@@ -0,0 +1,51 @@
+--
+-- patch-ipblocks-drop-ipb_by.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS ipblocks_tmp;
+CREATE TABLE /*_*/ipblocks_tmp (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by_actor bigint unsigned NOT NULL,
+  ipb_reason_id bigint unsigned NOT NULL,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0,
+  ipb_parent_block_id int default NULL,
+  ipb_sitewide bool NOT NULL default 1
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/ipblocks_tmp (
+       ipb_id, ipb_address, ipb_user, ipb_by_actor, ipb_reason_id,
+       ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+       ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+       ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id, ipb_sitewide
+  ) SELECT
+       ipb_id, ipb_address, ipb_user, ipb_by_actor, ipb_reason_id,
+       ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+       ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+       ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id, ipb_sitewide
+  FROM /*_*/ipblocks;
+
+DROP TABLE /*_*/ipblocks;
+ALTER TABLE /*_*/ipblocks_tmp RENAME TO /*_*/ipblocks;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-logging-drop-log_user.sql b/maintenance/sqlite/archives/patch-logging-drop-log_user.sql
new file mode 100644 (file)
index 0000000..0f5871a
--- /dev/null
@@ -0,0 +1,41 @@
+--
+-- patch-logging-drop-log_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/logging_tmp;
+CREATE TABLE /*_*/logging_tmp (
+  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  log_type varbinary(32) NOT NULL default '',
+  log_action varbinary(32) NOT NULL default '',
+  log_timestamp binary(14) NOT NULL default '19700101000000',
+  log_actor bigint unsigned NOT NULL DEFAULT 0,
+  log_namespace int NOT NULL default 0,
+  log_title varchar(255) binary NOT NULL default '',
+  log_page int unsigned NULL,
+  log_comment_id bigint unsigned NOT NULL,
+  log_params blob NOT NULL,
+  log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/logging_tmp (
+       log_id, log_type, log_action, log_timestamp, log_actor,
+       log_namespace, log_title, log_page, log_comment_id, log_params, log_deleted
+  ) SELECT
+       log_id, log_type, log_action, log_timestamp, log_actor,
+       log_namespace, log_title, log_page, log_comment_id, log_params, log_deleted
+  FROM /*_*/logging;
+
+DROP TABLE /*_*/logging;
+ALTER TABLE /*_*/logging_tmp RENAME TO /*_*/logging;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/log_type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-oldimage-drop-oi_user.sql b/maintenance/sqlite/archives/patch-oldimage-drop-oi_user.sql
new file mode 100644 (file)
index 0000000..8735238
--- /dev/null
@@ -0,0 +1,44 @@
+--
+-- patch-oldimage-drop-oi_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/oldimage_tmp;
+CREATE TABLE /*_*/oldimage_tmp (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description_id bigint unsigned NOT NULL,
+  oi_actor bigint unsigned NOT NULL,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+  oi_minor_mime varbinary(100) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/oldimage_tmp (
+       oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+       oi_description_id, oi_actor, oi_timestamp, oi_metadata,
+       oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1
+  ) SELECT
+       oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+       oi_description_id, oi_actor, oi_timestamp, oi_metadata,
+       oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1
+  FROM /*_*/oldimage;
+
+DROP TABLE /*_*/oldimage;
+ALTER TABLE /*_*/oldimage_tmp RENAME TO /*_*/oldimage;
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-recentchanges-drop-rc_user.sql b/maintenance/sqlite/archives/patch-recentchanges-drop-rc_user.sql
new file mode 100644 (file)
index 0000000..ba1f434
--- /dev/null
@@ -0,0 +1,59 @@
+--
+-- patch-recentchanges-drop-rc_user.sql
+--
+-- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields.
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/recentchanges_tmp;
+CREATE TABLE /*_*/recentchanges_tmp (
+  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rc_timestamp varbinary(14) NOT NULL default '',
+  rc_actor bigint unsigned NOT NULL DEFAULT 0,
+  rc_namespace int NOT NULL default 0,
+  rc_title varchar(255) binary NOT NULL default '',
+  rc_comment_id bigint unsigned NOT NULL,
+  rc_minor tinyint unsigned NOT NULL default 0,
+  rc_bot tinyint unsigned NOT NULL default 0,
+  rc_new tinyint unsigned NOT NULL default 0,
+  rc_cur_id int unsigned NOT NULL default 0,
+  rc_this_oldid int unsigned NOT NULL default 0,
+  rc_last_oldid int unsigned NOT NULL default 0,
+  rc_type tinyint unsigned NOT NULL default 0,
+  rc_source varchar(16) binary not null default '',
+  rc_patrolled tinyint unsigned NOT NULL default 0,
+  rc_ip varbinary(40) NOT NULL default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint unsigned NOT NULL default 0,
+  rc_logid int unsigned NOT NULL default 0,
+  rc_log_type varbinary(255) NULL default NULL,
+  rc_log_action varbinary(255) NULL default NULL,
+  rc_params blob NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/recentchanges_tmp (
+       rc_id, rc_timestamp, rc_actor, rc_namespace, rc_title,
+       rc_comment_id, rc_minor, rc_bot, rc_new, rc_cur_id, rc_this_oldid, rc_last_oldid,
+       rc_type, rc_source, rc_patrolled, rc_ip, rc_old_len, rc_new_len, rc_deleted,
+       rc_logid, rc_log_type, rc_log_action, rc_params
+  ) SELECT
+       rc_id, rc_timestamp, rc_actor, rc_namespace, rc_title,
+       rc_comment_id, rc_minor, rc_bot, rc_new, rc_cur_id, rc_this_oldid, rc_last_oldid,
+       rc_type, rc_source, rc_patrolled, rc_ip, rc_old_len, rc_new_len, rc_deleted,
+       rc_logid, rc_log_type, rc_log_action, rc_params
+  FROM /*_*/recentchanges;
+
+DROP TABLE /*_*/recentchanges;
+ALTER TABLE /*_*/recentchanges_tmp RENAME TO /*_*/recentchanges;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title_timestamp ON /*_*/recentchanges (rc_namespace, rc_title, rc_timestamp);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
+CREATE INDEX /*i*/rc_name_type_patrolled_timestamp ON /*_*/recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
+CREATE INDEX /*i*/rc_this_oldid ON /*_*/recentchanges (rc_this_oldid);
+
+COMMIT;
index 979e68a..e3c6c02 100644 (file)
@@ -1,6 +1,6 @@
 -- Blobs table for external storage
 
-CREATE TABLE /*$wgDBprefix*/blobs (
+CREATE TABLE IF NOT EXISTS /*$wgDBprefix*/blobs (
        blob_id integer UNSIGNED NOT NULL AUTO_INCREMENT,
        blob_text longblob,
        PRIMARY KEY  (blob_id)
index 060f327..37625cf 100644 (file)
@@ -232,7 +232,7 @@ class CheckStorage {
                                }
                                foreach ( $externalConcatBlobs as $cluster => $xBlobIds ) {
                                        $blobIds = array_keys( $xBlobIds );
-                                       $extDb =& $this->dbStore->getSlave( $cluster );
+                                       $extDb =& $this->dbStore->getReplica( $cluster );
                                        $blobsTable = $this->dbStore->getTable( $extDb );
                                        $res = $extDb->select( $blobsTable,
                                                [ 'blob_id' ],
@@ -433,7 +433,7 @@ class CheckStorage {
 
                foreach ( $externalConcatBlobs as $cluster => $oldIds ) {
                        $blobIds = array_keys( $oldIds );
-                       $extDb =& $this->dbStore->getSlave( $cluster );
+                       $extDb =& $this->dbStore->getReplica( $cluster );
                        $blobsTable = $this->dbStore->getTable( $extDb );
                        $headerLength = strlen( self::CONCAT_HEADER );
                        $res = $extDb->select( $blobsTable,
index 316d2d2..9f20e67 100644 (file)
@@ -275,7 +275,7 @@ class RecompressTracked {
        /**
         * Dispatch a command to the next available replica DB.
         * This may block until a replica DB finishes its work and becomes available.
-        * @param array ...$args
+        * @param array|string ...$args
         */
        function dispatch( ...$args ) {
                $pipes = $this->replicaPipes;
@@ -713,6 +713,8 @@ class CgzCopyTransaction {
        /** @var ConcatenatedGzipHistoryBlob|false */
        public $cgz;
        public $referrers;
+       /** @var array */
+       private $texts;
 
        /**
         * Create a transaction from a RecompressTracked object
index df15abf..55c6775 100644 (file)
@@ -594,9 +594,7 @@ CREATE TABLE /*_*/archive (
 
   -- Basic revision stuff...
   ar_comment_id bigint unsigned NOT NULL,
-  ar_user int unsigned NOT NULL default 0, -- Deprecated in favor of ar_actor
-  ar_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of ar_actor
-  ar_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ar_user/ar_user_text should be used)
+  ar_actor bigint unsigned NOT NULL,
   ar_timestamp binary(14) NOT NULL default '',
   ar_minor_edit tinyint NOT NULL default 0,
 
@@ -661,7 +659,6 @@ CREATE TABLE /*_*/archive (
 CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
 
 -- Index for Special:DeletedContributions
-CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
 CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
 
 -- Index for linking archive rows with tables that normally link with revision
@@ -1034,14 +1031,8 @@ CREATE TABLE /*_*/ipblocks (
   -- Blocked user ID or 0 for IP blocks.
   ipb_user int unsigned NOT NULL default 0,
 
-  -- User ID who made the block.
-  ipb_by int unsigned NOT NULL default 0, -- Deprecated in favor of ipb_by_actor
-
-  -- User name of blocker
-  ipb_by_text varchar(255) binary NOT NULL default '', -- Deprecated in favor of ipb_by_actor
-
   -- Actor who made the block.
-  ipb_by_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ipb_by/ipb_by_text should be used)
+  ipb_by_actor bigint unsigned NOT NULL,
 
   -- Key to comment_id. Text comment made by blocker.
   ipb_reason_id bigint unsigned NOT NULL,
@@ -1177,14 +1168,8 @@ CREATE TABLE /*_*/image (
   -- This is displayed in image upload history and logs.
   img_description_id bigint unsigned NOT NULL,
 
-  -- user_id and user_name of uploader.
-  -- Deprecated in favor of img_actor.
-  img_user int unsigned NOT NULL default 0,
-  img_user_text varchar(255) binary NOT NULL DEFAULT '',
-
   -- actor_id of the uploader.
-  -- ("DEFAULT 0" is temporary, signaling that img_user/img_user_text should be used)
-  img_actor bigint unsigned NOT NULL DEFAULT 0,
+  img_actor bigint unsigned NOT NULL,
 
   -- Time of the upload.
   img_timestamp varbinary(14) NOT NULL default '',
@@ -1194,8 +1179,6 @@ CREATE TABLE /*_*/image (
 ) /*$wgDBTableOptions*/;
 
 -- Used by Special:Newimages and ApiQueryAllImages
-CREATE INDEX /*i*/img_user_timestamp ON /*_*/image (img_user,img_timestamp);
-CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
 CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor,img_timestamp);
 -- Used by Special:ListFiles for sort-by-size
 CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
@@ -1226,9 +1209,7 @@ CREATE TABLE /*_*/oldimage (
   oi_height int NOT NULL default 0,
   oi_bits int NOT NULL default 0,
   oi_description_id bigint unsigned NOT NULL,
-  oi_user int unsigned NOT NULL default 0, -- Deprecated in favor of oi_actor
-  oi_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of oi_actor
-  oi_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that oi_user/oi_user_text should be used)
+  oi_actor bigint unsigned NOT NULL,
   oi_timestamp binary(14) NOT NULL default '',
 
   oi_metadata mediumblob NOT NULL,
@@ -1239,7 +1220,6 @@ CREATE TABLE /*_*/oldimage (
   oi_sha1 varbinary(32) NOT NULL default ''
 ) /*$wgDBTableOptions*/;
 
-CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
 CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
 CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
 -- oi_archive_name truncated to 14 to avoid key length overflow
@@ -1287,9 +1267,7 @@ CREATE TABLE /*_*/filearchive (
   fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
   fa_minor_mime varbinary(100) default "unknown",
   fa_description_id bigint unsigned NOT NULL,
-  fa_user int unsigned default 0, -- Deprecated in favor of fa_actor
-  fa_user_text varchar(255) binary DEFAULT '', -- Deprecated in favor of fa_actor
-  fa_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that fa_user/fa_user_text should be used)
+  fa_actor bigint unsigned NOT NULL,
   fa_timestamp binary(14) default '',
 
   -- Visibility of deleted revisions, bitfield
@@ -1306,7 +1284,6 @@ CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_sto
 -- sort by deletion time
 CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
 -- sort by uploader
-CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
 CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
 -- find file by sha1, 10 bytes will be enough for hashes to be indexed
 CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
@@ -1378,9 +1355,7 @@ CREATE TABLE /*_*/recentchanges (
   rc_timestamp varbinary(14) NOT NULL default '',
 
   -- As in revision
-  rc_user int unsigned NOT NULL default 0, -- Deprecated in favor of rc_actor
-  rc_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of rc_actor
-  rc_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that rc_user/rc_user_text should be used)
+  rc_actor bigint unsigned NOT NULL,
 
   -- When pages are renamed, their RC entries do _not_ change.
   rc_namespace int NOT NULL default 0,
@@ -1461,11 +1436,9 @@ CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,
 CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
 
 -- Probably intended for Special:NewPages namespace filter
-CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
 CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
 
 -- SiteStats active user count, Special:ActiveUsers, Special:NewPages user filter
-CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
 CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
 
 -- ApiQueryRecentChanges (T140108)
@@ -1595,14 +1568,8 @@ CREATE TABLE /*_*/logging (
   -- Timestamp. Duh.
   log_timestamp binary(14) NOT NULL default '19700101000000',
 
-  -- The user who performed this action; key to user_id
-  log_user int unsigned NOT NULL default 0, -- Deprecated in favor of log_actor
-
-  -- Name of the user who performed this action
-  log_user_text varchar(255) binary NOT NULL default '', -- Deprecated in favor of log_actor
-
   -- The actor who performed this action
-  log_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that log_user/log_user_text should be used)
+  log_actor bigint unsigned NOT NULL,
 
   -- Key to the page affected. Where a user is the target,
   -- this will point to the user page.
@@ -1625,7 +1592,6 @@ CREATE TABLE /*_*/logging (
 CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
 
 -- Special:Log performer filter
-CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
 CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
 
 -- Special:Log title filter, log extract
@@ -1635,7 +1601,6 @@ CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_times
 CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
 
 -- Special:Log filter by performer and type
-CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
 CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
 
 -- Apparently just used for a few maintenance pages (findMissingFiles.php, Flow).
@@ -1645,12 +1610,6 @@ CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
 -- Special:Log action filter
 CREATE INDEX /*i*/log_type_action ON /*_*/logging (log_type, log_action, log_timestamp);
 
--- Special:Log filter by type and anonymous performer
-CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp);
-
--- Special:Log filter by anonymous performer
-CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp);
-
 
 CREATE TABLE /*_*/log_search (
   -- The type of ID (rev ID, log ID, rev timestamp, username)
index 98f1c24..2f8941f 100644 (file)
@@ -110,11 +110,10 @@ The new option is NOT validated.' );
                                        $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
                                }
                        } else {
-
                                foreach ( $defaultOptions as $name => $defaultValue ) {
                                        $userValue = $user->getOption( $name );
                                        if ( $userValue != $defaultValue ) {
-                                               $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
+                                               $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
                                        }
                                }
                        }
index b546cbf..6425238 100644 (file)
@@ -24,6 +24,8 @@
 // details of the session. Enforce this constraint with respect to session use.
 define( 'MW_NO_SESSION', 1 );
 
+define( 'MW_ENTRY_POINT', 'opensearch_desc' );
+
 require_once __DIR__ . '/includes/WebStart.php';
 
 if ( $wgRequest->getVal( 'ctype' ) == 'application/xml' ) {
index ff29ebf..1a05e81 100644 (file)
                </testsuite>
                <testsuite name="extensions:unit">
                        <directory>extensions/**/tests/phpunit/unit</directory>
+                       <directory>extensions/**/tests/phpunit/Unit</directory>
                </testsuite>
                <testsuite name="skins:unit">
                        <directory>skins/**/tests/phpunit/unit</directory>
+                       <directory>skins/**/tests/phpunit/Unit</directory>
                </testsuite>
                <testsuite name="core:integration">
                        <directory>tests/phpunit/integration</directory>
index dccdd38..7b652b7 100644 (file)
@@ -40,6 +40,8 @@
 // details of the session. Enforce this constraint with respect to session use.
 define( 'MW_NO_SESSION', 1 );
 
+define( 'MW_ENTRY_POINT', 'profileinfo' );
+
 ini_set( 'zlib.output_compression', 'off' );
 
 require __DIR__ . '/includes/WebStart.php';
index 8234e89..1388128 100644 (file)
@@ -156,12 +156,10 @@ return [
        /* jQuery Plugins */
 
        'jquery.accessKeyLabel' => [
-               'scripts' => 'resources/src/jquery/jquery.accessKeyLabel.js',
+               'deprecated' => 'Please use "mediawiki.util" instead.',
                'dependencies' => [
-                       'jquery.client',
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                ],
-               'messages' => [ 'brackets', 'word-separator' ],
                'targets' => [ 'mobile', 'desktop' ],
        ],
        'jquery.checkboxShiftClick' => [
@@ -214,7 +212,7 @@ return [
        'jquery.highlightText' => [
                'scripts' => 'resources/src/jquery/jquery.highlightText.js',
                'dependencies' => [
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
@@ -290,7 +288,7 @@ return [
                'messages' => [ 'sort-descending', 'sort-ascending' ],
                'dependencies' => [
                        'jquery.tablesorter.styles',
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                        'mediawiki.language.months',
                ],
        ],
@@ -757,7 +755,7 @@ return [
                ],
                'dependencies' => [
                        'mediawiki.language',
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
@@ -928,7 +926,7 @@ return [
                        'resources/src/mediawiki.htmlform/selectorother.js',
                ],
                'dependencies' => [
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                        'jquery.lengthLimit',
                ],
                'messages' => [
@@ -942,9 +940,6 @@ return [
                'scripts' => [
                        'resources/src/mediawiki.htmlform.checker.js',
                ],
-               'dependencies' => [
-                       'jquery.throttle-debounce',
-               ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.htmlform.ooui' => [
@@ -972,7 +967,7 @@ return [
                'scripts' => 'resources/src/mediawiki.inspect.js',
                'dependencies' => [
                        'mediawiki.String',
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
@@ -1032,8 +1027,11 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.RegExp' => [
-               'scripts' => 'resources/src/mediawiki.RegExp.js',
+               'deprecated' => 'Please use mw.util.escapeRegExp() instead.',
                'targets' => [ 'desktop', 'mobile' ],
+               'dependencies' => [
+                       'mediawiki.util',
+               ],
        ],
        'mediawiki.String' => [
                'scripts' => 'resources/src/mediawiki.String.js',
@@ -1254,19 +1252,20 @@ return [
                ]
        ],
        'mediawiki.util' => [
-               'localBasePath' => "$IP/resources/src",
-               'remoteBasePath' => "$wgResourceBasePath/resources/src",
+               'localBasePath' => "$IP/resources/src/mediawiki.util/",
+               'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.util/",
                'packageFiles' => [
-                       'mediawiki.util.js',
+                       'util.js',
+                       'jquery.accessKeyLabel.js',
                        [ 'name' => 'config.json', 'config' => [
                                'FragmentMode',
                                'LoadScript',
                        ] ],
                ],
                'dependencies' => [
-                       'jquery.accessKeyLabel',
-                       'mediawiki.RegExp',
+                       'jquery.client',
                ],
+               'messages' => [ 'brackets', 'word-separator' ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.viewport' => [
@@ -1663,8 +1662,8 @@ return [
        'mediawiki.page.ready' => [
                'scripts' => 'resources/src/mediawiki.page.ready.js',
                'dependencies' => [
-                       'jquery.accessKeyLabel',
                        'jquery.checkboxShiftClick',
+                       'mediawiki.util',
                        'mediawiki.notify',
                        'mediawiki.api'
                ],
@@ -1702,8 +1701,6 @@ return [
                        'mediawiki.util',
                        'mediawiki.Title',
                        'mediawiki.jqueryMsg',
-                       'jquery.accessKeyLabel',
-                       'mediawiki.RegExp',
                ],
                'messages' => [
                        'watch',
@@ -1860,11 +1857,7 @@ return [
                        'styles/mw.rcfilters.ui.FilterTagMultiselectWidgetMobile.less'
                ],
                'skinStyles' => [
-                       'vector' => [
-                               'styles/mw.rcfilters.ui.Overlay.vector.less',
-                       ],
                        'monobook' => [
-                               'styles/mw.rcfilters.ui.Overlay.monobook.less',
                                'styles/mw.rcfilters.ui.CapsuleItemWidget.monobook.less',
                                'styles/mw.rcfilters.ui.FilterMenuOptionWidget.monobook.less',
                        ],
@@ -2646,7 +2639,7 @@ return [
                        'period-pm',
                ],
                'dependencies' => [
-                       'mediawiki.RegExp',
+                       'mediawiki.util',
                        'oojs-ui-core',
                        'oojs-ui.styles.icons-moderation',
                        'oojs-ui.styles.icons-movement',
index 7a131da..a6c8cd7 100644 (file)
                        // Construct regexes for number identification
                        for ( i = 0; i < ascii.length; i++ ) {
                                ts.transformTable[ localised[ i ] ] = ascii[ i ];
-                               digits.push( mw.RegExp.escape( localised[ i ] ) );
+                               digits.push( mw.util.escapeRegExp( localised[ i ] ) );
                        }
                }
                digitClass = '[' + digits.join( '', digits ) + ']';
                for ( i = 0; i < 12; i++ ) {
                        name = mw.language.months.names[ i ].toLowerCase();
                        ts.monthNames[ name ] = i + 1;
-                       regex.push( mw.RegExp.escape( name ) );
+                       regex.push( mw.util.escapeRegExp( name ) );
                        name = mw.language.months.genitive[ i ].toLowerCase();
                        ts.monthNames[ name ] = i + 1;
-                       regex.push( mw.RegExp.escape( name ) );
+                       regex.push( mw.util.escapeRegExp( name ) );
                        name = mw.language.months.abbrev[ i ].toLowerCase().replace( '.', '' );
                        ts.monthNames[ name ] = i + 1;
-                       regex.push( mw.RegExp.escape( name ) );
+                       regex.push( mw.util.escapeRegExp( name ) );
                }
 
                // Build piped string
                if ( ts.collationTable ) {
                        // Build array of key names
                        for ( key in ts.collationTable ) {
-                               keys.push( mw.RegExp.escape( key ) );
+                               keys.push( mw.util.escapeRegExp( key ) );
                        }
                        if ( keys.length ) {
                                ts.collationRegex = new RegExp( keys.join( '|' ), 'ig' );
diff --git a/resources/src/jquery/jquery.accessKeyLabel.js b/resources/src/jquery/jquery.accessKeyLabel.js
deleted file mode 100644 (file)
index cdc5808..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-/**
- * jQuery plugin to update the tooltip to show the correct access key
- *
- * @class jQuery.plugin.accessKeyLabel
- */
-( function () {
-
-       // Cached access key modifiers for used browser
-       var cachedAccessKeyModifiers,
-
-               // Whether to use 'test-' instead of correct prefix (used for testing)
-               useTestPrefix = false,
-
-               // tag names which can have a label tag
-               // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content
-               labelable = 'button, input, textarea, keygen, meter, output, progress, select';
-
-       /**
-        * Find the modifier keys that need to be pressed together with the accesskey to trigger the input.
-        *
-        * The result is dependant on the ua paramater or the current platform.
-        * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here.
-        * Valid key values that are returned can be: ctrl, alt, option, shift, esc
-        *
-        * @private
-        * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
-        * @return {Array} Array with 1 or more of the string values, in this order: ctrl, option, alt, shift, esc
-        */
-       function getAccessKeyModifiers( ua ) {
-               var profile, accessKeyModifiers;
-
-               // use cached prefix if possible
-               if ( !ua && cachedAccessKeyModifiers ) {
-                       return cachedAccessKeyModifiers;
-               }
-
-               profile = $.client.profile( ua );
-
-               switch ( profile.name ) {
-                       case 'chrome':
-                       case 'opera':
-                               if ( profile.name === 'opera' && profile.versionNumber < 15 ) {
-                                       accessKeyModifiers = [ 'shift', 'esc' ];
-                               } else if ( profile.platform === 'mac' ) {
-                                       accessKeyModifiers = [ 'ctrl', 'option' ];
-                               } else {
-                                       // Chrome/Opera on Windows or Linux
-                                       // (both alt- and alt-shift work, but alt with E, D, F etc does not
-                                       // work since they are browser shortcuts)
-                                       accessKeyModifiers = [ 'alt', 'shift' ];
-                               }
-                               break;
-                       case 'firefox':
-                       case 'iceweasel':
-                               if ( profile.versionBase < 2 ) {
-                                       // Before v2, Firefox used alt, though it was rebindable in about:config
-                                       accessKeyModifiers = [ 'alt' ];
-                               } else {
-                                       if ( profile.platform === 'mac' ) {
-                                               if ( profile.versionNumber < 14 ) {
-                                                       accessKeyModifiers = [ 'ctrl' ];
-                                               } else {
-                                                       accessKeyModifiers = [ 'ctrl', 'option' ];
-                                               }
-                                       } else {
-                                               accessKeyModifiers = [ 'alt', 'shift' ];
-                                       }
-                               }
-                               break;
-                       case 'safari':
-                       case 'konqueror':
-                               if ( profile.platform === 'win' ) {
-                                       accessKeyModifiers = [ 'alt' ];
-                               } else {
-                                       if ( profile.layoutVersion > 526 ) {
-                                               // Non-Windows Safari with webkit_version > 526
-                                               accessKeyModifiers = [ 'ctrl', profile.platform === 'mac' ? 'option' : 'alt' ];
-                                       } else {
-                                               accessKeyModifiers = [ 'ctrl' ];
-                                       }
-                               }
-                               break;
-                       case 'msie':
-                       case 'edge':
-                               accessKeyModifiers = [ 'alt' ];
-                               break;
-                       default:
-                               accessKeyModifiers = profile.platform === 'mac' ? [ 'ctrl' ] : [ 'alt' ];
-                               break;
-               }
-
-               // cache modifiers
-               if ( !ua ) {
-                       cachedAccessKeyModifiers = accessKeyModifiers;
-               }
-               return accessKeyModifiers;
-       }
-
-       /**
-        * Get the access key label for an element.
-        *
-        * Will use native accessKeyLabel if available (currently only in Firefox 8+),
-        * falls back to #getAccessKeyModifiers.
-        *
-        * @private
-        * @param {HTMLElement} element Element to get the label for
-        * @return {string} Access key label
-        */
-       function getAccessKeyLabel( element ) {
-               // abort early if no access key
-               if ( !element.accessKey ) {
-                       return '';
-               }
-               // use accessKeyLabel if possible
-               // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel
-               if ( !useTestPrefix && element.accessKeyLabel ) {
-                       return element.accessKeyLabel;
-               }
-               return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey;
-       }
-
-       /**
-        * Update the title for an element (on the element with the access key or it's label) to show
-        * the correct access key label.
-        *
-        * @private
-        * @param {HTMLElement} element Element with the accesskey
-        * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
-        */
-       function updateTooltipOnElement( element, titleElement ) {
-               var oldTitle, parts, regexp, newTitle, accessKeyLabel,
-                       separatorMsg = mw.message( 'word-separator' ).plain();
-
-               oldTitle = titleElement.title;
-               if ( !oldTitle ) {
-                       // don't add a title if the element didn't have one before
-                       return;
-               }
-
-               parts = ( separatorMsg + mw.message( 'brackets' ).plain() ).split( '$1' );
-               regexp = new RegExp( parts.map( mw.RegExp.escape ).join( '.*?' ) + '$' );
-               newTitle = oldTitle.replace( regexp, '' );
-               accessKeyLabel = getAccessKeyLabel( element );
-
-               if ( accessKeyLabel ) {
-                       // Should be build the same as in Linker::titleAttrib
-                       newTitle += separatorMsg + mw.message( 'brackets', accessKeyLabel ).plain();
-               }
-               if ( oldTitle !== newTitle ) {
-                       titleElement.title = newTitle;
-               }
-       }
-
-       /**
-        * Update the title for an element to show the correct access key label.
-        *
-        * @private
-        * @param {HTMLElement} element Element with the accesskey
-        */
-       function updateTooltip( element ) {
-               var id, $element, $label, $labelParent;
-               updateTooltipOnElement( element, element );
-
-               // update associated label if there is one
-               $element = $( element );
-               if ( $element.is( labelable ) ) {
-                       // Search it using 'for' attribute
-                       id = element.id.replace( /"/g, '\\"' );
-                       if ( id ) {
-                               $label = $( 'label[for="' + id + '"]' );
-                               if ( $label.length === 1 ) {
-                                       updateTooltipOnElement( element, $label[ 0 ] );
-                               }
-                       }
-
-                       // Search it as parent, because the form control can also be inside the label element itself
-                       $labelParent = $element.parents( 'label' );
-                       if ( $labelParent.length === 1 ) {
-                               updateTooltipOnElement( element, $labelParent[ 0 ] );
-                       }
-               }
-       }
-
-       /**
-        * Update the titles for all elements in a jQuery selection.
-        *
-        * @return {jQuery}
-        * @chainable
-        */
-       $.fn.updateTooltipAccessKeys = function () {
-               return this.each( function () {
-                       updateTooltip( this );
-               } );
-       };
-
-       /**
-        * getAccessKeyModifiers
-        *
-        * @method updateTooltipAccessKeys_getAccessKeyModifiers
-        * @inheritdoc #getAccessKeyModifiers
-        */
-       $.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers;
-
-       /**
-        * getAccessKeyLabel
-        *
-        * @method updateTooltipAccessKeys_getAccessKeyLabel
-        * @inheritdoc #getAccessKeyLabel
-        */
-       $.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel;
-
-       /**
-        * getAccessKeyPrefix
-        *
-        * @method updateTooltipAccessKeys_getAccessKeyPrefix
-        * @deprecated since 1.27 Use #getAccessKeyModifiers
-        * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
-        * @return {string}
-        */
-       $.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) {
-               return getAccessKeyModifiers( ua ).join( '-' ) + '-';
-       };
-
-       /**
-        * Switch test mode on and off.
-        *
-        * @method updateTooltipAccessKeys_setTestMode
-        * @param {boolean} mode New mode
-        */
-       $.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) {
-               useTestPrefix = mode;
-       };
-
-       /**
-        * @class jQuery
-        * @mixins jQuery.plugin.accessKeyLabel
-        */
-
-}() );
index de08607..1fabb43 100644 (file)
@@ -17,7 +17,7 @@
                                }
                                $.highlightText.innerHighlight(
                                        node,
-                                       new RegExp( '(^|\\s)' + mw.RegExp.escape( words[ i ] ), 'i' )
+                                       new RegExp( '(^|\\s)' + mw.util.escapeRegExp( words[ i ] ), 'i' )
                                );
                        }
                        return node;
@@ -26,7 +26,7 @@
                prefixHighlight: function ( node, prefix ) {
                        $.highlightText.innerHighlight(
                                node,
-                               new RegExp( '(^)' + mw.RegExp.escape( prefix ), 'i' )
+                               new RegExp( '(^)' + mw.util.escapeRegExp( prefix ), 'i' )
                        );
                },
 
@@ -38,7 +38,7 @@
 
                        $.highlightText.innerHighlight(
                                node,
-                               new RegExp( '(^)' + mw.RegExp.escape( prefix ) + comboMarks + '*', 'i' )
+                               new RegExp( '(^)' + mw.util.escapeRegExp( prefix ) + comboMarks + '*', 'i' )
                        );
                },
 
diff --git a/resources/src/mediawiki.RegExp.js b/resources/src/mediawiki.RegExp.js
deleted file mode 100644 (file)
index 5323d4f..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-( function () {
-       /**
-        * @class mw.RegExp
-        */
-       mw.RegExp = {
-               /**
-                * Escape string for safe inclusion in regular expression
-                *
-                * The following characters are escaped:
-                *
-                *     \ { } ( ) | . ? * + - ^ $ [ ]
-                *
-                * @since 1.26
-                * @static
-                * @param {string} str String to escape
-                * @return {string} Escaped string
-                */
-               escape: function ( str ) {
-                       return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ); // eslint-disable-line no-useless-escape
-               }
-       };
-}() );
index 3084e12..3f39fd1 100644 (file)
@@ -137,11 +137,11 @@ var
                '[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' +
                // URL percent encoding sequences interfere with the ability
                // to round-trip titles -- you can't link to them consistently.
-               '|%[0-9A-Fa-f]{2}' +
+               '|%[\\dA-Fa-f]{2}' +
                // XML/HTML character references produce similar issues.
-               '|&[A-Za-z0-9\u0080-\uFFFF]+;' +
-               '|&#[0-9]+;' +
-               '|&#x[0-9A-Fa-f]+;'
+               '|&[\\dA-Za-z\u0080-\uFFFF]+;' +
+               '|&#\\d+;' +
+               '|&#x[\\dA-Fa-f]+;'
        ),
 
        // From MediaWikiTitleCodec::splitTitleString() in PHP
@@ -149,7 +149,7 @@ var
        rWhitespace = /[ _\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+/g,
 
        // From MediaWikiTitleCodec::splitTitleString() in PHP
-       rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]/g,
+       rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]+/g,
 
        /**
         * Slightly modified from Flinfo. Credit goes to Lupo and Flominator.
@@ -173,13 +173,13 @@ var
                },
                // URL encoding (possibly)
                {
-                       pattern: /%([0-9A-Fa-f]{2})/g,
+                       pattern: /%([\dA-Fa-f]{2})/g,
                        replace: '% $1',
                        generalRule: true
                },
                // HTML-character-entities
                {
-                       pattern: /&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g,
+                       pattern: /&(([\dA-Za-z\x80-\xff]+|#\d+|#x[\dA-Fa-f]+);)/g,
                        replace: '& $1',
                        generalRule: true
                },
@@ -228,7 +228,7 @@ var
         * @return {Object|boolean}
         */
        parse = function ( title, defaultNamespace ) {
-               var namespace, m, id, i, fragment, ext;
+               var namespace, m, id, i, fragment;
 
                namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
 
@@ -294,7 +294,7 @@ var
                }
 
                // Reject illegal characters
-               if ( title.match( rInvalid ) ) {
+               if ( rInvalid.test( title ) ) {
                        return false;
                }
 
@@ -336,21 +336,9 @@ var
                        return false;
                }
 
-               // For backwards-compatibility with old mw.Title, we separate the extension from the
-               // rest of the title.
-               i = title.lastIndexOf( '.' );
-               if ( i === -1 || title.length <= i + 1 ) {
-                       // Extensions are the non-empty segment after the last dot
-                       ext = null;
-               } else {
-                       ext = title.slice( i + 1 );
-                       title = title.slice( 0, i );
-               }
-
                return {
                        namespace: namespace,
                        title: title,
-                       ext: ext,
                        fragment: fragment
                };
        },
@@ -438,7 +426,6 @@ function Title( title, namespace ) {
 
        this.namespace = parsed.namespace;
        this.title = parsed.title;
-       this.ext = parsed.ext;
        this.fragment = parsed.fragment;
 }
 
@@ -465,7 +452,6 @@ Title.newFromText = function ( title, namespace ) {
        t = Object.create( Title.prototype );
        t.namespace = parsed.namespace;
        t.title = parsed.title;
-       t.ext = parsed.ext;
        t.fragment = parsed.fragment;
 
        return t;
@@ -600,7 +586,6 @@ Title.newFromUserInput = function ( title, defaultNamespaceOrOptions, options )
  * @return {mw.Title|null} A valid Title object or null if the title is invalid
  */
 Title.newFromFileName = function ( uncleanName ) {
-
        return Title.newFromUserInput( 'File:' + uncleanName, {
                forUploading: true
        } );
@@ -622,10 +607,10 @@ Title.newFromImg = function ( img ) {
                thumbPhpRegex = /thumb\.php/,
                regexes = [
                        // Thumbnails
-                       /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
+                       /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
 
                        // Full size images
-                       /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)$/,
+                       /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)$/,
 
                        // Thumbnails in non-hashed upload directories
                        /\/([^\s/]+)\/[^\s/]+-(?:\1|thumbnail)[^\s/]*$/,
@@ -638,9 +623,7 @@ Title.newFromImg = function ( img ) {
 
        src = img.jquery ? img[ 0 ].src : img.src;
 
-       matches = src.match( thumbPhpRegex );
-
-       if ( matches ) {
+       if ( thumbPhpRegex.test( src ) ) {
                return mw.Title.newFromText( 'File:' + mw.util.getParamValue( 'f', src ) );
        }
 
@@ -759,16 +742,16 @@ Title.exist = {
 Title.normalizeExtension = function ( extension ) {
        var
                lower = extension.toLowerCase(),
-               squish = {
+               normalizations = {
                        htm: 'html',
                        jpeg: 'jpg',
                        mpeg: 'mpg',
                        tiff: 'tif',
                        ogv: 'ogg'
                };
-       if ( Object.prototype.hasOwnProperty.call( squish, lower ) ) {
-               return squish[ lower ];
-       } else if ( /^[0-9a-z]+$/.test( lower ) ) {
+       if ( Object.hasOwnProperty.call( normalizations, lower ) ) {
+               return normalizations[ lower ];
+       } else if ( /^[\da-z]+$/.test( lower ) ) {
                return lower;
        } else {
                return '';
@@ -824,13 +807,11 @@ Title.prototype = {
         * @return {string}
         */
        getName: function () {
-               if (
-                       mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( this.namespace ) !== -1 ||
-                       !this.title.length
-               ) {
-                       return this.title;
+               var ext = this.getExtension();
+               if ( ext === null ) {
+                       return this.getMain();
                }
-               return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
+               return this.getMain().slice( 0, -ext.length - 1 );
        },
 
        /**
@@ -852,7 +833,11 @@ Title.prototype = {
         * @return {string|null} Name extension or null if there is none
         */
        getExtension: function () {
-               return this.ext;
+               var lastDot = this.title.lastIndexOf( '.' );
+               if ( lastDot === -1 ) {
+                       return null;
+               }
+               return this.title.slice( lastDot + 1 ) || null;
        },
 
        /**
@@ -863,7 +848,8 @@ Title.prototype = {
         * @return {string}
         */
        getDotExtension: function () {
-               return this.ext === null ? '' : '.' + this.ext;
+               var ext = this.getExtension();
+               return ext === null ? '' : '.' + ext;
        },
 
        /**
@@ -874,7 +860,13 @@ Title.prototype = {
         * @return {string}
         */
        getMain: function () {
-               return this.getName() + this.getDotExtension();
+               if (
+                       mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( this.namespace ) !== -1 ||
+                       !this.title.length
+               ) {
+                       return this.title;
+               }
+               return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
        },
 
        /**
index b0887fa..2ba08cf 100644 (file)
@@ -1,6 +1,8 @@
 {
        "ß": "ß",
        "ʼn": "ʼn",
+       "ƀ": "ƀ",
+       "ƚ": "ƚ",
        "Dž": "Dž",
        "dž": "Dž",
        "Lj": "Lj",
        "ǰ": "ǰ",
        "Dz": "Dz",
        "dz": "Dz",
-       "ʝ": "Ʝ",
+       "ȼ": "ȼ",
+       "ȿ": "ȿ",
+       "ɀ": "ɀ",
+       "ɂ": "ɂ",
+       "ɇ": "ɇ",
+       "ɉ": "ɉ",
+       "ɋ": "ɋ",
+       "ɍ": "ɍ",
+       "ɏ": "ɏ",
+       "ɐ": "ɐ",
+       "ɑ": "ɑ",
+       "ɒ": "ɒ",
+       "ɜ": "ɜ",
+       "ɡ": "ɡ",
+       "ɥ": "ɥ",
+       "ɦ": "ɦ",
+       "ɪ": "ɪ",
+       "ɫ": "ɫ",
+       "ɬ": "ɬ",
+       "ɱ": "ɱ",
+       "ɽ": "ɽ",
+       "ʂ": "ʂ",
+       "ʇ": "ʇ",
+       "ʉ": "ʉ",
+       "ʌ": "ʌ",
+       "ʝ": "ʝ",
+       "ʞ": "ʞ",
        "ͅ": "ͅ",
+       "ͱ": "ͱ",
+       "ͳ": "ͳ",
+       "ͷ": "ͷ",
+       "ͻ": "ͻ",
+       "ͼ": "ͼ",
+       "ͽ": "ͽ",
        "ΐ": "ΐ",
        "ΰ": "ΰ",
+       "ϗ": "ϗ",
+       "ϲ": "Σ",
+       "ϳ": "ϳ",
+       "ϸ": "ϸ",
+       "ϻ": "ϻ",
+       "ӏ": "ӏ",
+       "ӷ": "ӷ",
+       "ӻ": "ӻ",
+       "ӽ": "ӽ",
+       "ӿ": "ӿ",
+       "ԑ": "ԑ",
+       "ԓ": "ԓ",
+       "ԕ": "ԕ",
+       "ԗ": "ԗ",
+       "ԙ": "ԙ",
+       "ԛ": "ԛ",
+       "ԝ": "ԝ",
+       "ԟ": "ԟ",
+       "ԡ": "ԡ",
+       "ԣ": "ԣ",
+       "ԥ": "ԥ",
+       "ԧ": "ԧ",
+       "ԩ": "ԩ",
+       "ԫ": "ԫ",
+       "ԭ": "ԭ",
+       "ԯ": "ԯ",
        "և": "և",
-       "ᏸ": "Ᏸ",
-       "ᏹ": "Ᏹ",
-       "ᏺ": "Ᏺ",
-       "ᏻ": "Ᏻ",
-       "ᏼ": "Ᏼ",
-       "ᏽ": "Ᏽ",
+       "ა": "ა",
+       "ბ": "ბ",
+       "გ": "გ",
+       "დ": "დ",
+       "ე": "ე",
+       "ვ": "ვ",
+       "ზ": "ზ",
+       "თ": "თ",
+       "ი": "ი",
+       "კ": "კ",
+       "ლ": "ლ",
+       "მ": "მ",
+       "ნ": "ნ",
+       "ო": "ო",
+       "პ": "პ",
+       "ჟ": "ჟ",
+       "რ": "რ",
+       "ს": "ს",
+       "ტ": "ტ",
+       "უ": "უ",
+       "ფ": "ფ",
+       "ქ": "ქ",
+       "ღ": "ღ",
+       "ყ": "ყ",
+       "შ": "შ",
+       "ჩ": "ჩ",
+       "ც": "ც",
+       "ძ": "ძ",
+       "წ": "წ",
+       "ჭ": "ჭ",
+       "ხ": "ხ",
+       "ჯ": "ჯ",
+       "ჰ": "ჰ",
+       "ჱ": "ჱ",
+       "ჲ": "ჲ",
+       "ჳ": "ჳ",
+       "ჴ": "ჴ",
+       "ჵ": "ჵ",
+       "ჶ": "ჶ",
+       "ჷ": "ჷ",
+       "ჸ": "ჸ",
+       "ჹ": "ჹ",
+       "ჺ": "ჺ",
+       "ჽ": "ჽ",
+       "ჾ": "ჾ",
+       "ჿ": "ჿ",
+       "ᏸ": "ᏸ",
+       "ᏹ": "ᏹ",
+       "ᏺ": "ᏺ",
+       "ᏻ": "ᏻ",
+       "ᏼ": "ᏼ",
+       "ᏽ": "ᏽ",
+       "ᲀ": "ᲀ",
+       "ᲁ": "ᲁ",
+       "ᲂ": "ᲂ",
+       "ᲃ": "ᲃ",
+       "ᲄ": "ᲄ",
+       "ᲅ": "ᲅ",
+       "ᲆ": "ᲆ",
+       "ᲇ": "ᲇ",
+       "ᲈ": "ᲈ",
+       "ᵹ": "ᵹ",
+       "ᵽ": "ᵽ",
+       "ᶎ": "ᶎ",
        "ẖ": "ẖ",
        "ẗ": "ẗ",
        "ẘ": "ẘ",
        "ẙ": "ẙ",
        "ẚ": "ẚ",
+       "ỻ": "ỻ",
+       "ỽ": "ỽ",
+       "ỿ": "ỿ",
        "ὐ": "ὐ",
        "ὒ": "ὒ",
        "ὔ": "ὔ",
        "ῶ": "ῶ",
        "ῷ": "ῷ",
        "ῼ": "ῼ",
+       "ⅎ": "ⅎ",
        "ⅰ": "ⅰ",
        "ⅱ": "ⅱ",
        "ⅲ": "ⅲ",
        "ⅽ": "ⅽ",
        "ⅾ": "ⅾ",
        "ⅿ": "ⅿ",
+       "ↄ": "ↄ",
        "ⓐ": "ⓐ",
        "ⓑ": "ⓑ",
        "ⓒ": "ⓒ",
        "ⓧ": "ⓧ",
        "ⓨ": "ⓨ",
        "ⓩ": "ⓩ",
-       "ꞵ": "Ꞵ",
-       "ꞷ": "Ꞷ",
-       "ꭓ": "Ꭓ",
-       "ꭰ": "Ꭰ",
-       "ꭱ": "Ꭱ",
-       "ꭲ": "Ꭲ",
-       "ꭳ": "Ꭳ",
-       "ꭴ": "Ꭴ",
-       "ꭵ": "Ꭵ",
-       "ꭶ": "Ꭶ",
-       "ꭷ": "Ꭷ",
-       "ꭸ": "Ꭸ",
-       "ꭹ": "Ꭹ",
-       "ꭺ": "Ꭺ",
-       "ꭻ": "Ꭻ",
-       "ꭼ": "Ꭼ",
-       "ꭽ": "Ꭽ",
-       "ꭾ": "Ꭾ",
-       "ꭿ": "Ꭿ",
-       "ꮀ": "Ꮀ",
-       "ꮁ": "Ꮁ",
-       "ꮂ": "Ꮂ",
-       "ꮃ": "Ꮃ",
-       "ꮄ": "Ꮄ",
-       "ꮅ": "Ꮅ",
-       "ꮆ": "Ꮆ",
-       "ꮇ": "Ꮇ",
-       "ꮈ": "Ꮈ",
-       "ꮉ": "Ꮉ",
-       "ꮊ": "Ꮊ",
-       "ꮋ": "Ꮋ",
-       "ꮌ": "Ꮌ",
-       "ꮍ": "Ꮍ",
-       "ꮎ": "Ꮎ",
-       "ꮏ": "Ꮏ",
-       "ꮐ": "Ꮐ",
-       "ꮑ": "Ꮑ",
-       "ꮒ": "Ꮒ",
-       "ꮓ": "Ꮓ",
-       "ꮔ": "Ꮔ",
-       "ꮕ": "Ꮕ",
-       "ꮖ": "Ꮖ",
-       "ꮗ": "Ꮗ",
-       "ꮘ": "Ꮘ",
-       "ꮙ": "Ꮙ",
-       "ꮚ": "Ꮚ",
-       "ꮛ": "Ꮛ",
-       "ꮜ": "Ꮜ",
-       "ꮝ": "Ꮝ",
-       "ꮞ": "Ꮞ",
-       "ꮟ": "Ꮟ",
-       "ꮠ": "Ꮠ",
-       "ꮡ": "Ꮡ",
-       "ꮢ": "Ꮢ",
-       "ꮣ": "Ꮣ",
-       "ꮤ": "Ꮤ",
-       "ꮥ": "Ꮥ",
-       "ꮦ": "Ꮦ",
-       "ꮧ": "Ꮧ",
-       "ꮨ": "Ꮨ",
-       "ꮩ": "Ꮩ",
-       "ꮪ": "Ꮪ",
-       "ꮫ": "Ꮫ",
-       "ꮬ": "Ꮬ",
-       "ꮭ": "Ꮭ",
-       "ꮮ": "Ꮮ",
-       "ꮯ": "Ꮯ",
-       "ꮰ": "Ꮰ",
-       "ꮱ": "Ꮱ",
-       "ꮲ": "Ꮲ",
-       "ꮳ": "Ꮳ",
-       "ꮴ": "Ꮴ",
-       "ꮵ": "Ꮵ",
-       "ꮶ": "Ꮶ",
-       "ꮷ": "Ꮷ",
-       "ꮸ": "Ꮸ",
-       "ꮹ": "Ꮹ",
-       "ꮺ": "Ꮺ",
-       "ꮻ": "Ꮻ",
-       "ꮼ": "Ꮼ",
-       "ꮽ": "Ꮽ",
-       "ꮾ": "Ꮾ",
-       "ꮿ": "Ꮿ",
+       "ⰰ": "ⰰ",
+       "ⰱ": "ⰱ",
+       "ⰲ": "ⰲ",
+       "ⰳ": "ⰳ",
+       "ⰴ": "ⰴ",
+       "ⰵ": "ⰵ",
+       "ⰶ": "ⰶ",
+       "ⰷ": "ⰷ",
+       "ⰸ": "ⰸ",
+       "ⰹ": "ⰹ",
+       "ⰺ": "ⰺ",
+       "ⰻ": "ⰻ",
+       "ⰼ": "ⰼ",
+       "ⰽ": "ⰽ",
+       "ⰾ": "ⰾ",
+       "ⰿ": "ⰿ",
+       "ⱀ": "ⱀ",
+       "ⱁ": "ⱁ",
+       "ⱂ": "ⱂ",
+       "ⱃ": "ⱃ",
+       "ⱄ": "ⱄ",
+       "ⱅ": "ⱅ",
+       "ⱆ": "ⱆ",
+       "ⱇ": "ⱇ",
+       "ⱈ": "ⱈ",
+       "ⱉ": "ⱉ",
+       "ⱊ": "ⱊ",
+       "ⱋ": "ⱋ",
+       "ⱌ": "ⱌ",
+       "ⱍ": "ⱍ",
+       "ⱎ": "ⱎ",
+       "ⱏ": "ⱏ",
+       "ⱐ": "ⱐ",
+       "ⱑ": "ⱑ",
+       "ⱒ": "ⱒ",
+       "ⱓ": "ⱓ",
+       "ⱔ": "ⱔ",
+       "ⱕ": "ⱕ",
+       "ⱖ": "ⱖ",
+       "ⱗ": "ⱗ",
+       "ⱘ": "ⱘ",
+       "ⱙ": "ⱙ",
+       "ⱚ": "ⱚ",
+       "ⱛ": "ⱛ",
+       "ⱜ": "ⱜ",
+       "ⱝ": "ⱝ",
+       "ⱞ": "ⱞ",
+       "ⱡ": "ⱡ",
+       "ⱥ": "ⱥ",
+       "ⱦ": "ⱦ",
+       "ⱨ": "ⱨ",
+       "ⱪ": "ⱪ",
+       "ⱬ": "ⱬ",
+       "ⱳ": "ⱳ",
+       "ⱶ": "ⱶ",
+       "ⲁ": "ⲁ",
+       "ⲃ": "ⲃ",
+       "ⲅ": "ⲅ",
+       "ⲇ": "ⲇ",
+       "ⲉ": "ⲉ",
+       "ⲋ": "ⲋ",
+       "ⲍ": "ⲍ",
+       "ⲏ": "ⲏ",
+       "ⲑ": "ⲑ",
+       "ⲓ": "ⲓ",
+       "ⲕ": "ⲕ",
+       "ⲗ": "ⲗ",
+       "ⲙ": "ⲙ",
+       "ⲛ": "ⲛ",
+       "ⲝ": "ⲝ",
+       "ⲟ": "ⲟ",
+       "ⲡ": "ⲡ",
+       "ⲣ": "ⲣ",
+       "ⲥ": "ⲥ",
+       "ⲧ": "ⲧ",
+       "ⲩ": "ⲩ",
+       "ⲫ": "ⲫ",
+       "ⲭ": "ⲭ",
+       "ⲯ": "ⲯ",
+       "ⲱ": "ⲱ",
+       "ⲳ": "ⲳ",
+       "ⲵ": "ⲵ",
+       "ⲷ": "ⲷ",
+       "ⲹ": "ⲹ",
+       "ⲻ": "ⲻ",
+       "ⲽ": "ⲽ",
+       "ⲿ": "ⲿ",
+       "ⳁ": "ⳁ",
+       "ⳃ": "ⳃ",
+       "ⳅ": "ⳅ",
+       "ⳇ": "ⳇ",
+       "ⳉ": "ⳉ",
+       "ⳋ": "ⳋ",
+       "ⳍ": "ⳍ",
+       "ⳏ": "ⳏ",
+       "ⳑ": "ⳑ",
+       "ⳓ": "ⳓ",
+       "ⳕ": "ⳕ",
+       "ⳗ": "ⳗ",
+       "ⳙ": "ⳙ",
+       "ⳛ": "ⳛ",
+       "ⳝ": "ⳝ",
+       "ⳟ": "ⳟ",
+       "ⳡ": "ⳡ",
+       "ⳣ": "ⳣ",
+       "ⳬ": "ⳬ",
+       "ⳮ": "ⳮ",
+       "ⳳ": "ⳳ",
+       "ⴀ": "ⴀ",
+       "ⴁ": "ⴁ",
+       "ⴂ": "ⴂ",
+       "ⴃ": "ⴃ",
+       "ⴄ": "ⴄ",
+       "ⴅ": "ⴅ",
+       "ⴆ": "ⴆ",
+       "ⴇ": "ⴇ",
+       "ⴈ": "ⴈ",
+       "ⴉ": "ⴉ",
+       "ⴊ": "ⴊ",
+       "ⴋ": "ⴋ",
+       "ⴌ": "ⴌ",
+       "ⴍ": "ⴍ",
+       "ⴎ": "ⴎ",
+       "ⴏ": "ⴏ",
+       "ⴐ": "ⴐ",
+       "ⴑ": "ⴑ",
+       "ⴒ": "ⴒ",
+       "ⴓ": "ⴓ",
+       "ⴔ": "ⴔ",
+       "ⴕ": "ⴕ",
+       "ⴖ": "ⴖ",
+       "ⴗ": "ⴗ",
+       "ⴘ": "ⴘ",
+       "ⴙ": "ⴙ",
+       "ⴚ": "ⴚ",
+       "ⴛ": "ⴛ",
+       "ⴜ": "ⴜ",
+       "ⴝ": "ⴝ",
+       "ⴞ": "ⴞ",
+       "ⴟ": "ⴟ",
+       "ⴠ": "ⴠ",
+       "ⴡ": "ⴡ",
+       "ⴢ": "ⴢ",
+       "ⴣ": "ⴣ",
+       "ⴤ": "ⴤ",
+       "ⴥ": "ⴥ",
+       "ⴧ": "ⴧ",
+       "ⴭ": "ⴭ",
+       "ꙁ": "ꙁ",
+       "ꙃ": "ꙃ",
+       "ꙅ": "ꙅ",
+       "ꙇ": "ꙇ",
+       "ꙉ": "ꙉ",
+       "ꙋ": "ꙋ",
+       "ꙍ": "ꙍ",
+       "ꙏ": "ꙏ",
+       "ꙑ": "ꙑ",
+       "ꙓ": "ꙓ",
+       "ꙕ": "ꙕ",
+       "ꙗ": "ꙗ",
+       "ꙙ": "ꙙ",
+       "ꙛ": "ꙛ",
+       "ꙝ": "ꙝ",
+       "ꙟ": "ꙟ",
+       "ꙡ": "ꙡ",
+       "ꙣ": "ꙣ",
+       "ꙥ": "ꙥ",
+       "ꙧ": "ꙧ",
+       "ꙩ": "ꙩ",
+       "ꙫ": "ꙫ",
+       "ꙭ": "ꙭ",
+       "ꚁ": "ꚁ",
+       "ꚃ": "ꚃ",
+       "ꚅ": "ꚅ",
+       "ꚇ": "ꚇ",
+       "ꚉ": "ꚉ",
+       "ꚋ": "ꚋ",
+       "ꚍ": "ꚍ",
+       "ꚏ": "ꚏ",
+       "ꚑ": "ꚑ",
+       "ꚓ": "ꚓ",
+       "ꚕ": "ꚕ",
+       "ꚗ": "ꚗ",
+       "ꚙ": "ꚙ",
+       "ꚛ": "ꚛ",
+       "ꜣ": "ꜣ",
+       "ꜥ": "ꜥ",
+       "ꜧ": "ꜧ",
+       "ꜩ": "ꜩ",
+       "ꜫ": "ꜫ",
+       "ꜭ": "ꜭ",
+       "ꜯ": "ꜯ",
+       "ꜳ": "ꜳ",
+       "ꜵ": "ꜵ",
+       "ꜷ": "ꜷ",
+       "ꜹ": "ꜹ",
+       "ꜻ": "ꜻ",
+       "ꜽ": "ꜽ",
+       "ꜿ": "ꜿ",
+       "ꝁ": "ꝁ",
+       "ꝃ": "ꝃ",
+       "ꝅ": "ꝅ",
+       "ꝇ": "ꝇ",
+       "ꝉ": "ꝉ",
+       "ꝋ": "ꝋ",
+       "ꝍ": "ꝍ",
+       "ꝏ": "ꝏ",
+       "ꝑ": "ꝑ",
+       "ꝓ": "ꝓ",
+       "ꝕ": "ꝕ",
+       "ꝗ": "ꝗ",
+       "ꝙ": "ꝙ",
+       "ꝛ": "ꝛ",
+       "ꝝ": "ꝝ",
+       "ꝟ": "ꝟ",
+       "ꝡ": "ꝡ",
+       "ꝣ": "ꝣ",
+       "ꝥ": "ꝥ",
+       "ꝧ": "ꝧ",
+       "ꝩ": "ꝩ",
+       "ꝫ": "ꝫ",
+       "ꝭ": "ꝭ",
+       "ꝯ": "ꝯ",
+       "ꝺ": "ꝺ",
+       "ꝼ": "ꝼ",
+       "ꝿ": "ꝿ",
+       "ꞁ": "ꞁ",
+       "ꞃ": "ꞃ",
+       "ꞅ": "ꞅ",
+       "ꞇ": "ꞇ",
+       "ꞌ": "ꞌ",
+       "ꞑ": "ꞑ",
+       "ꞓ": "ꞓ",
+       "ꞔ": "ꞔ",
+       "ꞗ": "ꞗ",
+       "ꞙ": "ꞙ",
+       "ꞛ": "ꞛ",
+       "ꞝ": "ꞝ",
+       "ꞟ": "ꞟ",
+       "ꞡ": "ꞡ",
+       "ꞣ": "ꞣ",
+       "ꞥ": "ꞥ",
+       "ꞧ": "ꞧ",
+       "ꞩ": "ꞩ",
+       "ꞵ": "ꞵ",
+       "ꞷ": "ꞷ",
+       "ꞹ": "ꞹ",
+       "ꞻ": "ꞻ",
+       "ꞽ": "ꞽ",
+       "ꞿ": "ꞿ",
+       "ꟃ": "ꟃ",
+       "ꭓ": "ꭓ",
+       "ꭰ": "ꭰ",
+       "ꭱ": "ꭱ",
+       "ꭲ": "ꭲ",
+       "ꭳ": "ꭳ",
+       "ꭴ": "ꭴ",
+       "ꭵ": "ꭵ",
+       "ꭶ": "ꭶ",
+       "ꭷ": "ꭷ",
+       "ꭸ": "ꭸ",
+       "ꭹ": "ꭹ",
+       "ꭺ": "ꭺ",
+       "ꭻ": "ꭻ",
+       "ꭼ": "ꭼ",
+       "ꭽ": "ꭽ",
+       "ꭾ": "ꭾ",
+       "ꭿ": "ꭿ",
+       "ꮀ": "ꮀ",
+       "ꮁ": "ꮁ",
+       "ꮂ": "ꮂ",
+       "ꮃ": "ꮃ",
+       "ꮄ": "ꮄ",
+       "ꮅ": "ꮅ",
+       "ꮆ": "ꮆ",
+       "ꮇ": "ꮇ",
+       "ꮈ": "ꮈ",
+       "ꮉ": "ꮉ",
+       "ꮊ": "ꮊ",
+       "ꮋ": "ꮋ",
+       "ꮌ": "ꮌ",
+       "ꮍ": "ꮍ",
+       "ꮎ": "ꮎ",
+       "ꮏ": "ꮏ",
+       "ꮐ": "ꮐ",
+       "ꮑ": "ꮑ",
+       "ꮒ": "ꮒ",
+       "ꮓ": "ꮓ",
+       "ꮔ": "ꮔ",
+       "ꮕ": "ꮕ",
+       "ꮖ": "ꮖ",
+       "ꮗ": "ꮗ",
+       "ꮘ": "ꮘ",
+       "ꮙ": "ꮙ",
+       "ꮚ": "ꮚ",
+       "ꮛ": "ꮛ",
+       "ꮜ": "ꮜ",
+       "ꮝ": "ꮝ",
+       "ꮞ": "ꮞ",
+       "ꮟ": "ꮟ",
+       "ꮠ": "ꮠ",
+       "ꮡ": "ꮡ",
+       "ꮢ": "ꮢ",
+       "ꮣ": "ꮣ",
+       "ꮤ": "ꮤ",
+       "ꮥ": "ꮥ",
+       "ꮦ": "ꮦ",
+       "ꮧ": "ꮧ",
+       "ꮨ": "ꮨ",
+       "ꮩ": "ꮩ",
+       "ꮪ": "ꮪ",
+       "ꮫ": "ꮫ",
+       "ꮬ": "ꮬ",
+       "ꮭ": "ꮭ",
+       "ꮮ": "ꮮ",
+       "ꮯ": "ꮯ",
+       "ꮰ": "ꮰ",
+       "ꮱ": "ꮱ",
+       "ꮲ": "ꮲ",
+       "ꮳ": "ꮳ",
+       "ꮴ": "ꮴ",
+       "ꮵ": "ꮵ",
+       "ꮶ": "ꮶ",
+       "ꮷ": "ꮷ",
+       "ꮸ": "ꮸ",
+       "ꮹ": "ꮹ",
+       "ꮺ": "ꮺ",
+       "ꮻ": "ꮻ",
+       "ꮼ": "ꮼ",
+       "ꮽ": "ꮽ",
+       "ꮾ": "ꮾ",
+       "ꮿ": "ꮿ",
        "ff": "ff",
        "fi": "fi",
        "fl": "fl",
        "ﬔ": "ﬔ",
        "ﬕ": "ﬕ",
        "ﬖ": "ﬖ",
-       "ﬗ": "ﬗ"
+       "ﬗ": "ﬗ",
+       "𐑎": "𐑎",
+       "𐑏": "𐑏",
+       "𐓘": "𐓘",
+       "𐓙": "𐓙",
+       "𐓚": "𐓚",
+       "𐓛": "𐓛",
+       "𐓜": "𐓜",
+       "𐓝": "𐓝",
+       "𐓞": "𐓞",
+       "𐓟": "𐓟",
+       "𐓠": "𐓠",
+       "𐓡": "𐓡",
+       "𐓢": "𐓢",
+       "𐓣": "𐓣",
+       "𐓤": "𐓤",
+       "𐓥": "𐓥",
+       "𐓦": "𐓦",
+       "𐓧": "𐓧",
+       "𐓨": "𐓨",
+       "𐓩": "𐓩",
+       "𐓪": "𐓪",
+       "𐓫": "𐓫",
+       "𐓬": "𐓬",
+       "𐓭": "𐓭",
+       "𐓮": "𐓮",
+       "𐓯": "𐓯",
+       "𐓰": "𐓰",
+       "𐓱": "𐓱",
+       "𐓲": "𐓲",
+       "𐓳": "𐓳",
+       "𐓴": "𐓴",
+       "𐓵": "𐓵",
+       "𐓶": "𐓶",
+       "𐓷": "𐓷",
+       "𐓸": "𐓸",
+       "𐓹": "𐓹",
+       "𐓺": "𐓺",
+       "𐓻": "𐓻",
+       "𐳀": "𐳀",
+       "𐳁": "𐳁",
+       "𐳂": "𐳂",
+       "𐳃": "𐳃",
+       "𐳄": "𐳄",
+       "𐳅": "𐳅",
+       "𐳆": "𐳆",
+       "𐳇": "𐳇",
+       "𐳈": "𐳈",
+       "𐳉": "𐳉",
+       "𐳊": "𐳊",
+       "𐳋": "𐳋",
+       "𐳌": "𐳌",
+       "𐳍": "𐳍",
+       "𐳎": "𐳎",
+       "𐳏": "𐳏",
+       "𐳐": "𐳐",
+       "𐳑": "𐳑",
+       "𐳒": "𐳒",
+       "𐳓": "𐳓",
+       "𐳔": "𐳔",
+       "𐳕": "𐳕",
+       "𐳖": "𐳖",
+       "𐳗": "𐳗",
+       "𐳘": "𐳘",
+       "𐳙": "𐳙",
+       "𐳚": "𐳚",
+       "𐳛": "𐳛",
+       "𐳜": "𐳜",
+       "𐳝": "𐳝",
+       "𐳞": "𐳞",
+       "𐳟": "𐳟",
+       "𐳠": "𐳠",
+       "𐳡": "𐳡",
+       "𐳢": "𐳢",
+       "𐳣": "𐳣",
+       "𐳤": "𐳤",
+       "𐳥": "𐳥",
+       "𐳦": "𐳦",
+       "𐳧": "𐳧",
+       "𐳨": "𐳨",
+       "𐳩": "𐳩",
+       "𐳪": "𐳪",
+       "𐳫": "𐳫",
+       "𐳬": "𐳬",
+       "𐳭": "𐳭",
+       "𐳮": "𐳮",
+       "𐳯": "𐳯",
+       "𐳰": "𐳰",
+       "𐳱": "𐳱",
+       "𐳲": "𐳲",
+       "𑣀": "𑣀",
+       "𑣁": "𑣁",
+       "𑣂": "𑣂",
+       "𑣃": "𑣃",
+       "𑣄": "𑣄",
+       "𑣅": "𑣅",
+       "𑣆": "𑣆",
+       "𑣇": "𑣇",
+       "𑣈": "𑣈",
+       "𑣉": "𑣉",
+       "𑣊": "𑣊",
+       "𑣋": "𑣋",
+       "𑣌": "𑣌",
+       "𑣍": "𑣍",
+       "𑣎": "𑣎",
+       "𑣏": "𑣏",
+       "𑣐": "𑣐",
+       "𑣑": "𑣑",
+       "𑣒": "𑣒",
+       "𑣓": "𑣓",
+       "𑣔": "𑣔",
+       "𑣕": "𑣕",
+       "𑣖": "𑣖",
+       "𑣗": "𑣗",
+       "𑣘": "𑣘",
+       "𑣙": "𑣙",
+       "𑣚": "𑣚",
+       "𑣛": "𑣛",
+       "𑣜": "𑣜",
+       "𑣝": "𑣝",
+       "𑣞": "𑣞",
+       "𑣟": "𑣟",
+       "𖹠": "𖹠",
+       "𖹡": "𖹡",
+       "𖹢": "𖹢",
+       "𖹣": "𖹣",
+       "𖹤": "𖹤",
+       "𖹥": "𖹥",
+       "𖹦": "𖹦",
+       "𖹧": "𖹧",
+       "𖹨": "𖹨",
+       "𖹩": "𖹩",
+       "𖹪": "𖹪",
+       "𖹫": "𖹫",
+       "𖹬": "𖹬",
+       "𖹭": "𖹭",
+       "𖹮": "𖹮",
+       "𖹯": "𖹯",
+       "𖹰": "𖹰",
+       "𖹱": "𖹱",
+       "𖹲": "𖹲",
+       "𖹳": "𖹳",
+       "𖹴": "𖹴",
+       "𖹵": "𖹵",
+       "𖹶": "𖹶",
+       "𖹷": "𖹷",
+       "𖹸": "𖹸",
+       "𖹹": "𖹹",
+       "𖹺": "𖹺",
+       "𖹻": "𖹻",
+       "𖹼": "𖹼",
+       "𖹽": "𖹽",
+       "𖹾": "𖹾",
+       "𖹿": "𖹿",
+       "𞤢": "𞤢",
+       "𞤣": "𞤣",
+       "𞤤": "𞤤",
+       "𞤥": "𞤥",
+       "𞤦": "𞤦",
+       "𞤧": "𞤧",
+       "𞤨": "𞤨",
+       "𞤩": "𞤩",
+       "𞤪": "𞤪",
+       "𞤫": "𞤫",
+       "𞤬": "𞤬",
+       "𞤭": "𞤭",
+       "𞤮": "𞤮",
+       "𞤯": "𞤯",
+       "𞤰": "𞤰",
+       "𞤱": "𞤱",
+       "𞤲": "𞤲",
+       "𞤳": "𞤳",
+       "𞤴": "𞤴",
+       "𞤵": "𞤵",
+       "𞤶": "𞤶",
+       "𞤷": "𞤷",
+       "𞤸": "𞤸",
+       "𞤹": "𞤹",
+       "𞤺": "𞤺",
+       "𞤻": "𞤻",
+       "𞤼": "𞤼",
+       "𞤽": "𞤽",
+       "𞤾": "𞤾",
+       "𞤿": "𞤿",
+       "𞥀": "𞥀",
+       "𞥁": "𞥁",
+       "𞥂": "𞥂",
+       "𞥃": "𞥃"
 }
index 674584b..33207d4 100644 (file)
@@ -3,6 +3,14 @@
        // FIXME: mw.htmlform.Element also sets this to empty object
        mw.htmlform = {};
 
+       function debounce( delay, callback ) {
+               var timeout;
+               return function () {
+                       clearTimeout( timeout );
+                       timeout = setTimeout( Function.prototype.apply.bind( callback, this, arguments ), delay );
+               };
+       }
+
        /**
         * @class mw.htmlform.Checker
         */
@@ -52,7 +60,7 @@
                if ( $extraElements ) {
                        $e = $e.add( $extraElements );
                }
-               $e.on( events, $.debounce( 1000, this.validate.bind( this ) ) );
+               $e.on( events, debounce( 1000, this.validate.bind( this ) ) );
 
                return this;
        };
index 99eebae..9cafcd4 100644 (file)
@@ -16,7 +16,7 @@
                var $li,
                        $ul = $createButton.prev( 'ul.mw-htmlform-cloner-ul' ),
                        html = $ul.data( 'template' ).replace(
-                               new RegExp( mw.RegExp.escape( $ul.data( 'uniqueId' ) ), 'g' ),
+                               new RegExp( mw.util.escapeRegExp( $ul.data( 'uniqueId' ) ), 'g' ),
                                'clone' + ( ++cloneCounter )
                        );
 
index 7f4af5b..c7b99b2 100644 (file)
         */
        inspect.grep = function ( pattern ) {
                if ( typeof pattern.test !== 'function' ) {
-                       pattern = new RegExp( mw.RegExp.escape( pattern ), 'g' );
+                       pattern = new RegExp( mw.util.escapeRegExp( pattern ), 'g' );
                }
 
                return inspect.getLoadedModules().filter( function ( moduleName ) {
index f550a91..6b04b3c 100644 (file)
@@ -91,7 +91,7 @@
                actionPaths = mw.config.get( 'wgActionPaths' );
                for ( key in actionPaths ) {
                        parts = actionPaths[ key ].split( '$1' );
-                       parts = parts.map( mw.RegExp.escape );
+                       parts = parts.map( mw.util.escapeRegExp );
                        m = new RegExp( parts.join( '(.+)' ) ).exec( url );
                        if ( m && m[ 1 ] ) {
                                return key;
index 06840da..c883309 100644 (file)
@@ -1,8 +1,6 @@
 .mw-rcfilters-ui-overlay {
-       font-size: 0.875em;
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
-       z-index: 1;
 }
diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.monobook.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.monobook.less
deleted file mode 100644 (file)
index fae2b32..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-.mw-rcfilters-ui-overlay {
-       font-size: 1.28em; /* 0.8em / x-small */
-}
diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.vector.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.vector.less
deleted file mode 100644 (file)
index 528707b..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-// Fix z-index for the overlay in Vector, see T183442
-.mw-rcfilters-ui-overlay {
-       z-index: 101;
-}
index 31edb77..85794cd 100644 (file)
@@ -42,7 +42,7 @@ MainWrapperWidget = function MwRcfiltersUiMainWrapperWidget(
        this.$filtersContainer = config.$filtersContainer;
        this.$changesListContainer = config.$changesListContainer;
        this.$formContainer = config.$formContainer;
-       this.$overlay = $( '<div>' ).addClass( 'mw-rcfilters-ui-overlay' );
+       this.$overlay = $( '<div>' ).addClass( 'mw-rcfilters-ui-overlay oo-ui-defaultOverlay' );
        this.$wrapper = config.$wrapper || this.$element;
 
        this.savedLinksListWidget = new SavedLinksListWidget(
diff --git a/resources/src/mediawiki.util.js b/resources/src/mediawiki.util.js
deleted file mode 100644 (file)
index 36a0195..0000000
+++ /dev/null
@@ -1,565 +0,0 @@
-( function () {
-       'use strict';
-
-       var util,
-               config = require( './config.json' );
-
-       /**
-        * Encode the string like PHP's rawurlencode
-        * @ignore
-        *
-        * @param {string} str String to be encoded.
-        * @return {string} Encoded string
-        */
-       function rawurlencode( str ) {
-               str = String( str );
-               return encodeURIComponent( str )
-                       .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
-                       .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
-       }
-
-       /**
-        * Private helper function used by util.escapeId*()
-        * @ignore
-        *
-        * @param {string} str String to be encoded
-        * @param {string} mode Encoding mode, see documentation for $wgFragmentMode
-        *     in DefaultSettings.php
-        * @return {string} Encoded string
-        */
-       function escapeIdInternal( str, mode ) {
-               str = String( str );
-
-               switch ( mode ) {
-                       case 'html5':
-                               return str.replace( / /g, '_' );
-                       case 'legacy':
-                               return rawurlencode( str.replace( / /g, '_' ) )
-                                       .replace( /%3A/g, ':' )
-                                       .replace( /%/g, '.' );
-                       default:
-                               throw new Error( 'Unrecognized ID escaping mode ' + mode );
-               }
-       }
-
-       /**
-        * Utility library
-        * @class mw.util
-        * @singleton
-        */
-       util = {
-
-               /**
-                * Encode the string like PHP's rawurlencode
-                *
-                * @param {string} str String to be encoded.
-                * @return {string} Encoded string
-                */
-               rawurlencode: rawurlencode,
-
-               /**
-                * Encode string into HTML id compatible form suitable for use in HTML
-                * Analog to PHP Sanitizer::escapeIdForAttribute()
-                *
-                * @since 1.30
-                *
-                * @param {string} str String to encode
-                * @return {string} Encoded string
-                */
-               escapeIdForAttribute: function ( str ) {
-                       var mode = config.FragmentMode[ 0 ];
-
-                       return escapeIdInternal( str, mode );
-               },
-
-               /**
-                * Encode string into HTML id compatible form suitable for use in links
-                * Analog to PHP Sanitizer::escapeIdForLink()
-                *
-                * @since 1.30
-                *
-                * @param {string} str String to encode
-                * @return {string} Encoded string
-                */
-               escapeIdForLink: function ( str ) {
-                       var mode = config.FragmentMode[ 0 ];
-
-                       return escapeIdInternal( str, mode );
-               },
-
-               /**
-                * Encode page titles for use in a URL
-                *
-                * We want / and : to be included as literal characters in our title URLs
-                * as they otherwise fatally break the title.
-                *
-                * The others are decoded because we can, it's prettier and matches behaviour
-                * of `wfUrlencode` in PHP.
-                *
-                * @param {string} str String to be encoded.
-                * @return {string} Encoded string
-                */
-               wikiUrlencode: function ( str ) {
-                       return util.rawurlencode( str )
-                               .replace( /%20/g, '_' )
-                               // wfUrlencode replacements
-                               .replace( /%3B/g, ';' )
-                               .replace( /%40/g, '@' )
-                               .replace( /%24/g, '$' )
-                               .replace( /%21/g, '!' )
-                               .replace( /%2A/g, '*' )
-                               .replace( /%28/g, '(' )
-                               .replace( /%29/g, ')' )
-                               .replace( /%2C/g, ',' )
-                               .replace( /%2F/g, '/' )
-                               .replace( /%7E/g, '~' )
-                               .replace( /%3A/g, ':' );
-               },
-
-               /**
-                * Get the link to a page name (relative to `wgServer`),
-                *
-                * @param {string|null} [pageName=wgPageName] Page name
-                * @param {Object} [params] A mapping of query parameter names to values,
-                *  e.g. `{ action: 'edit' }`
-                * @return {string} Url of the page with name of `pageName`
-                */
-               getUrl: function ( pageName, params ) {
-                       var titleFragmentStart, url, query,
-                               fragment = '',
-                               title = typeof pageName === 'string' ? pageName : mw.config.get( 'wgPageName' );
-
-                       // Find any fragment
-                       titleFragmentStart = title.indexOf( '#' );
-                       if ( titleFragmentStart !== -1 ) {
-                               fragment = title.slice( titleFragmentStart + 1 );
-                               // Exclude the fragment from the page name
-                               title = title.slice( 0, titleFragmentStart );
-                       }
-
-                       // Produce query string
-                       if ( params ) {
-                               query = $.param( params );
-                       }
-                       if ( query ) {
-                               url = title ?
-                                       util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query :
-                                       util.wikiScript() + '?' + query;
-                       } else {
-                               url = mw.config.get( 'wgArticlePath' )
-                                       .replace( '$1', util.wikiUrlencode( title ).replace( /\$/g, '$$$$' ) );
-                       }
-
-                       // Append the encoded fragment
-                       if ( fragment.length ) {
-                               url += '#' + util.escapeIdForLink( fragment );
-                       }
-
-                       return url;
-               },
-
-               /**
-                * Get address to a script in the wiki root.
-                * For index.php use `mw.config.get( 'wgScript' )`.
-                *
-                * @since 1.18
-                * @param {string} str Name of script (e.g. 'api'), defaults to 'index'
-                * @return {string} Address to script (e.g. '/w/api.php' )
-                */
-               wikiScript: function ( str ) {
-                       str = str || 'index';
-                       if ( str === 'index' ) {
-                               return mw.config.get( 'wgScript' );
-                       } else if ( str === 'load' ) {
-                               return config.LoadScript;
-                       } else {
-                               return mw.config.get( 'wgScriptPath' ) + '/' + str + '.php';
-                       }
-               },
-
-               /**
-                * Append a new style block to the head and return the CSSStyleSheet object.
-                * Use .ownerNode to access the `<style>` element, or use mw.loader#addStyleTag.
-                * This function returns the styleSheet object for convience (due to cross-browsers
-                * difference as to where it is located).
-                *
-                *     var sheet = util.addCSS( '.foobar { display: none; }' );
-                *     $( foo ).click( function () {
-                *         // Toggle the sheet on and off
-                *         sheet.disabled = !sheet.disabled;
-                *     } );
-                *
-                * @param {string} text CSS to be appended
-                * @return {CSSStyleSheet} Use .ownerNode to get to the `<style>` element.
-                */
-               addCSS: function ( text ) {
-                       var s = mw.loader.addStyleTag( text );
-                       return s.sheet || s.styleSheet || s;
-               },
-
-               /**
-                * Grab the URL parameter value for the given parameter.
-                * Returns null if not found.
-                *
-                * @param {string} param The parameter name.
-                * @param {string} [url=location.href] URL to search through, defaulting to the current browsing location.
-                * @return {Mixed} Parameter value or null.
-                */
-               getParamValue: function ( param, url ) {
-                       // Get last match, stop at hash
-                       var re = new RegExp( '^[^#]*[&?]' + mw.RegExp.escape( param ) + '=([^&#]*)' ),
-                               m = re.exec( url !== undefined ? url : location.href );
-
-                       if ( m ) {
-                               // Beware that decodeURIComponent is not required to understand '+'
-                               // by spec, as encodeURIComponent does not produce it.
-                               return decodeURIComponent( m[ 1 ].replace( /\+/g, '%20' ) );
-                       }
-                       return null;
-               },
-
-               /**
-                * The content wrapper of the skin (e.g. `.mw-body`).
-                *
-                * Populated on document ready. To use this property,
-                * wait for `$.ready` and be sure to have a module dependency on
-                * `mediawiki.util` which will ensure
-                * your document ready handler fires after initialization.
-                *
-                * Because of the lazy-initialised nature of this property,
-                * you're discouraged from using it.
-                *
-                * If you need just the wikipage content (not any of the
-                * extra elements output by the skin), use `$( '#mw-content-text' )`
-                * instead. Or listen to mw.hook#wikipage_content which will
-                * allow your code to re-run when the page changes (e.g. live preview
-                * or re-render after ajax save).
-                *
-                * @property {jQuery}
-                */
-               $content: null,
-
-               /**
-                * Add a link to a portlet menu on the page, such as:
-                *
-                * p-cactions (Content actions), p-personal (Personal tools),
-                * p-navigation (Navigation), p-tb (Toolbox)
-                *
-                * The first three parameters are required, the others are optional and
-                * may be null. Though providing an id and tooltip is recommended.
-                *
-                * By default the new link will be added to the end of the list. To
-                * add the link before a given existing item, pass the DOM node
-                * (e.g. `document.getElementById( 'foobar' )`) or a jQuery-selector
-                * (e.g. `'#foobar'`) for that item.
-                *
-                *     util.addPortletLink(
-                *         'p-tb', 'https://www.mediawiki.org/',
-                *         'mediawiki.org', 't-mworg', 'Go to mediawiki.org', 'm', '#t-print'
-                *     );
-                *
-                *     var node = util.addPortletLink(
-                *         'p-tb',
-                *         new mw.Title( 'Special:Example' ).getUrl(),
-                *         'Example'
-                *     );
-                *     $( node ).on( 'click', function ( e ) {
-                *         console.log( 'Example' );
-                *         e.preventDefault();
-                *     } );
-                *
-                * @param {string} portletId ID of the target portlet (e.g. 'p-cactions' or 'p-personal')
-                * @param {string} href Link URL
-                * @param {string} text Link text
-                * @param {string} [id] ID of the list item, should be unique and preferably have
-                *  the appropriate prefix ('ca-', 'pt-', 'n-' or 't-')
-                * @param {string} [tooltip] Text to show when hovering over the link, without accesskey suffix
-                * @param {string} [accesskey] Access key to activate this link. One character only,
-                *  avoid conflicts with other links. Use `$( '[accesskey=x]' )` in the console to
-                *  see if 'x' is already used.
-                * @param {HTMLElement|jQuery|string} [nextnode] Element that the new item should be added before.
-                *  Must be another item in the same list, it will be ignored otherwise.
-                *  Can be specified as DOM reference, as jQuery object, or as CSS selector string.
-                * @return {HTMLElement|null} The added list item, or null if no element was added.
-                */
-               addPortletLink: function ( portletId, href, text, id, tooltip, accesskey, nextnode ) {
-                       var item, link, $portlet, portlet, portletDiv, ul, next;
-
-                       if ( !portletId ) {
-                               // Avoid confusing id="undefined" lookup
-                               return null;
-                       }
-
-                       portlet = document.getElementById( portletId );
-                       if ( !portlet ) {
-                               // Invalid portlet ID
-                               return null;
-                       }
-
-                       // Setup the anchor tag and set any the properties
-                       link = document.createElement( 'a' );
-                       link.href = href;
-                       link.textContent = text;
-                       if ( tooltip ) {
-                               link.title = tooltip;
-                       }
-                       if ( accesskey ) {
-                               link.accessKey = accesskey;
-                       }
-
-                       // Unhide portlet if it was hidden before
-                       $portlet = $( portlet );
-                       $portlet.removeClass( 'emptyPortlet' );
-
-                       // Setup the list item (and a span if $portlet is a Vector tab)
-                       // eslint-disable-next-line no-jquery/no-class-state
-                       if ( $portlet.hasClass( 'vectorTabs' ) ) {
-                               item = $( '<li>' ).append( $( '<span>' ).append( link )[ 0 ] )[ 0 ];
-                       } else {
-                               item = $( '<li>' ).append( link )[ 0 ];
-                       }
-                       if ( id ) {
-                               item.id = id;
-                       }
-
-                       // Select the first (most likely only) unordered list inside the portlet
-                       ul = portlet.querySelector( 'ul' );
-                       if ( !ul ) {
-                               // If it didn't have an unordered list yet, create one
-                               ul = document.createElement( 'ul' );
-                               portletDiv = portlet.querySelector( 'div' );
-                               if ( portletDiv ) {
-                                       // Support: Legacy skins have a div (such as div.body or div.pBody).
-                                       // Append the <ul> to that.
-                                       portletDiv.appendChild( ul );
-                               } else {
-                                       // Append it to the portlet directly
-                                       portlet.appendChild( ul );
-                               }
-                       }
-
-                       if ( nextnode && ( typeof nextnode === 'string' || nextnode.nodeType || nextnode.jquery ) ) {
-                               nextnode = $( ul ).find( nextnode );
-                               if ( nextnode.length === 1 && nextnode[ 0 ].parentNode === ul ) {
-                                       // Insertion point: Before nextnode
-                                       nextnode.before( item );
-                                       next = true;
-                               }
-                               // Else: Invalid nextnode value (no match, more than one match, or not a direct child)
-                               // Else: Invalid nextnode type
-                       }
-
-                       if ( !next ) {
-                               // Insertion point: End of list (default)
-                               ul.appendChild( item );
-                       }
-
-                       // Update tooltip for the access key after inserting into DOM
-                       // to get a localized access key label (T69946).
-                       if ( accesskey ) {
-                               $( link ).updateTooltipAccessKeys();
-                       }
-
-                       return item;
-               },
-
-               /**
-                * Validate a string as representing a valid e-mail address
-                * according to HTML5 specification. Please note the specification
-                * does not validate a domain with one character.
-                *
-                * FIXME: should be moved to or replaced by a validation module.
-                *
-                * @param {string} mailtxt E-mail address to be validated.
-                * @return {boolean|null} Null if `mailtxt` was an empty string, otherwise true/false
-                * as determined by validation.
-                */
-               validateEmail: function ( mailtxt ) {
-                       var rfc5322Atext, rfc1034LdhStr, html5EmailRegexp;
-
-                       if ( mailtxt === '' ) {
-                               return null;
-                       }
-
-                       // HTML5 defines a string as valid e-mail address if it matches
-                       // the ABNF:
-                       //     1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str )
-                       // With:
-                       // - atext   : defined in RFC 5322 section 3.2.3
-                       // - ldh-str : defined in RFC 1034 section 3.5
-                       //
-                       // (see STD 68 / RFC 5234 https://tools.ietf.org/html/std68)
-                       // First, define the RFC 5322 'atext' which is pretty easy:
-                       // atext = ALPHA / DIGIT / ; Printable US-ASCII
-                       //     "!" / "#" /    ; characters not including
-                       //     "$" / "%" /    ; specials. Used for atoms.
-                       //     "&" / "'" /
-                       //     "*" / "+" /
-                       //     "-" / "/" /
-                       //     "=" / "?" /
-                       //     "^" / "_" /
-                       //     "`" / "{" /
-                       //     "|" / "}" /
-                       //     "~"
-                       rfc5322Atext = 'a-z0-9!#$%&\'*+\\-/=?^_`{|}~';
-
-                       // Next define the RFC 1034 'ldh-str'
-                       //     <domain> ::= <subdomain> | " "
-                       //     <subdomain> ::= <label> | <subdomain> "." <label>
-                       //     <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
-                       //     <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
-                       //     <let-dig-hyp> ::= <let-dig> | "-"
-                       //     <let-dig> ::= <letter> | <digit>
-                       rfc1034LdhStr = 'a-z0-9\\-';
-
-                       html5EmailRegexp = new RegExp(
-                               // start of string
-                               '^' +
-                               // User part which is liberal :p
-                               '[' + rfc5322Atext + '\\.]+' +
-                               // 'at'
-                               '@' +
-                               // Domain first part
-                               '[' + rfc1034LdhStr + ']+' +
-                               // Optional second part and following are separated by a dot
-                               '(?:\\.[' + rfc1034LdhStr + ']+)*' +
-                               // End of string
-                               '$',
-                               // RegExp is case insensitive
-                               'i'
-                       );
-                       return ( mailtxt.match( html5EmailRegexp ) !== null );
-               },
-
-               /**
-                * Note: borrows from IP::isIPv4
-                *
-                * @param {string} address
-                * @param {boolean} [allowBlock=false]
-                * @return {boolean}
-                */
-               isIPv4Address: function ( address, allowBlock ) {
-                       var block, RE_IP_BYTE, RE_IP_ADD;
-
-                       if ( typeof address !== 'string' ) {
-                               return false;
-                       }
-
-                       block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '';
-                       RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])';
-                       RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
-
-                       return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) );
-               },
-
-               /**
-                * Note: borrows from IP::isIPv6
-                *
-                * @param {string} address
-                * @param {boolean} [allowBlock=false]
-                * @return {boolean}
-                */
-               isIPv6Address: function ( address, allowBlock ) {
-                       var block, RE_IPV6_ADD;
-
-                       if ( typeof address !== 'string' ) {
-                               return false;
-                       }
-
-                       block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '';
-                       RE_IPV6_ADD =
-                               '(?:' + // starts with "::" (including "::")
-                                       ':(?::|(?::' +
-                                               '[0-9A-Fa-f]{1,4}' +
-                                       '){1,7})' +
-                                       '|' + // ends with "::" (except "::")
-                                       '[0-9A-Fa-f]{1,4}' +
-                                       '(?::' +
-                                               '[0-9A-Fa-f]{1,4}' +
-                                       '){0,6}::' +
-                                       '|' + // contains no "::"
-                                       '[0-9A-Fa-f]{1,4}' +
-                                       '(?::' +
-                                               '[0-9A-Fa-f]{1,4}' +
-                                       '){7}' +
-                               ')';
-
-                       if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) {
-                               return true;
-                       }
-
-                       // contains one "::" in the middle (single '::' check below)
-                       RE_IPV6_ADD =
-                               '[0-9A-Fa-f]{1,4}' +
-                               '(?:::?' +
-                                       '[0-9A-Fa-f]{1,4}' +
-                               '){1,6}';
-
-                       return (
-                               new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) &&
-                               /::/.test( address ) &&
-                               !/::.*::/.test( address )
-                       );
-               },
-
-               /**
-                * Check whether a string is an IP address
-                *
-                * @since 1.25
-                * @param {string} address String to check
-                * @param {boolean} [allowBlock=false] If a block of IPs should be allowed
-                * @return {boolean}
-                */
-               isIPAddress: function ( address, allowBlock ) {
-                       return util.isIPv4Address( address, allowBlock ) ||
-                               util.isIPv6Address( address, allowBlock );
-               }
-       };
-
-       // Not allowed outside unit tests
-       if ( window.QUnit ) {
-               util.setOptionsForTest = function ( opts ) {
-                       var oldConfig = config;
-                       config = $.extend( {}, config, opts );
-                       return oldConfig;
-               };
-       }
-
-       /**
-        * Initialisation of mw.util.$content
-        */
-       function init() {
-               util.$content = ( function () {
-                       var i, l, $node, selectors;
-
-                       selectors = [
-                               // The preferred standard is class "mw-body".
-                               // You may also use class "mw-body mw-body-primary" if you use
-                               // mw-body in multiple locations. Or class "mw-body-primary" if
-                               // you use mw-body deeper in the DOM.
-                               '.mw-body-primary',
-                               '.mw-body',
-
-                               // If the skin has no such class, fall back to the parser output
-                               '#mw-content-text'
-                       ];
-
-                       for ( i = 0, l = selectors.length; i < l; i++ ) {
-                               $node = $( selectors[ i ] );
-                               if ( $node.length ) {
-                                       return $node.first();
-                               }
-                       }
-
-                       // Should never happen... well, it could if someone is not finished writing a
-                       // skin and has not yet inserted bodytext yet.
-                       return $( 'body' );
-               }() );
-       }
-
-       $( init );
-
-       mw.util = util;
-       module.exports = util;
-
-}() );
diff --git a/resources/src/mediawiki.util/.eslintrc.json b/resources/src/mediawiki.util/.eslintrc.json
new file mode 100644 (file)
index 0000000..ad8dbb3
--- /dev/null
@@ -0,0 +1,5 @@
+{
+       "parserOptions": {
+               "sourceType": "module"
+       }
+}
diff --git a/resources/src/mediawiki.util/jquery.accessKeyLabel.js b/resources/src/mediawiki.util/jquery.accessKeyLabel.js
new file mode 100644 (file)
index 0000000..07a06bf
--- /dev/null
@@ -0,0 +1,236 @@
+/**
+ * jQuery plugin to update the tooltip to show the correct access key
+ *
+ * @class jQuery.plugin.accessKeyLabel
+ */
+
+// Cached access key modifiers for used browser
+var cachedAccessKeyModifiers,
+
+       // Whether to use 'test-' instead of correct prefix (used for testing)
+       useTestPrefix = false,
+
+       // tag names which can have a label tag
+       // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content
+       labelable = 'button, input, textarea, keygen, meter, output, progress, select';
+
+/**
+ * Find the modifier keys that need to be pressed together with the accesskey to trigger the input.
+ *
+ * The result is dependant on the ua paramater or the current platform.
+ * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here.
+ * Valid key values that are returned can be: ctrl, alt, option, shift, esc
+ *
+ * @private
+ * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
+ * @return {Array} Array with 1 or more of the string values, in this order: ctrl, option, alt, shift, esc
+ */
+function getAccessKeyModifiers( ua ) {
+       var profile, accessKeyModifiers;
+
+       // use cached prefix if possible
+       if ( !ua && cachedAccessKeyModifiers ) {
+               return cachedAccessKeyModifiers;
+       }
+
+       profile = $.client.profile( ua );
+
+       switch ( profile.name ) {
+               case 'chrome':
+               case 'opera':
+                       if ( profile.name === 'opera' && profile.versionNumber < 15 ) {
+                               accessKeyModifiers = [ 'shift', 'esc' ];
+                       } else if ( profile.platform === 'mac' ) {
+                               accessKeyModifiers = [ 'ctrl', 'option' ];
+                       } else {
+                               // Chrome/Opera on Windows or Linux
+                               // (both alt- and alt-shift work, but alt with E, D, F etc does not
+                               // work since they are browser shortcuts)
+                               accessKeyModifiers = [ 'alt', 'shift' ];
+                       }
+                       break;
+               case 'firefox':
+               case 'iceweasel':
+                       if ( profile.versionBase < 2 ) {
+                               // Before v2, Firefox used alt, though it was rebindable in about:config
+                               accessKeyModifiers = [ 'alt' ];
+                       } else {
+                               if ( profile.platform === 'mac' ) {
+                                       if ( profile.versionNumber < 14 ) {
+                                               accessKeyModifiers = [ 'ctrl' ];
+                                       } else {
+                                               accessKeyModifiers = [ 'ctrl', 'option' ];
+                                       }
+                               } else {
+                                       accessKeyModifiers = [ 'alt', 'shift' ];
+                               }
+                       }
+                       break;
+               case 'safari':
+               case 'konqueror':
+                       if ( profile.platform === 'win' ) {
+                               accessKeyModifiers = [ 'alt' ];
+                       } else {
+                               if ( profile.layoutVersion > 526 ) {
+                                       // Non-Windows Safari with webkit_version > 526
+                                       accessKeyModifiers = [ 'ctrl', profile.platform === 'mac' ? 'option' : 'alt' ];
+                               } else {
+                                       accessKeyModifiers = [ 'ctrl' ];
+                               }
+                       }
+                       break;
+               case 'msie':
+               case 'edge':
+                       accessKeyModifiers = [ 'alt' ];
+                       break;
+               default:
+                       accessKeyModifiers = profile.platform === 'mac' ? [ 'ctrl' ] : [ 'alt' ];
+                       break;
+       }
+
+       // cache modifiers
+       if ( !ua ) {
+               cachedAccessKeyModifiers = accessKeyModifiers;
+       }
+       return accessKeyModifiers;
+}
+
+/**
+ * Get the access key label for an element.
+ *
+ * Will use native accessKeyLabel if available (currently only in Firefox 8+),
+ * falls back to #getAccessKeyModifiers.
+ *
+ * @private
+ * @param {HTMLElement} element Element to get the label for
+ * @return {string} Access key label
+ */
+function getAccessKeyLabel( element ) {
+       // abort early if no access key
+       if ( !element.accessKey ) {
+               return '';
+       }
+       // use accessKeyLabel if possible
+       // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel
+       if ( !useTestPrefix && element.accessKeyLabel ) {
+               return element.accessKeyLabel;
+       }
+       return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey;
+}
+
+/**
+ * Update the title for an element (on the element with the access key or it's label) to show
+ * the correct access key label.
+ *
+ * @private
+ * @param {HTMLElement} element Element with the accesskey
+ * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
+ */
+function updateTooltipOnElement( element, titleElement ) {
+       var oldTitle, parts, regexp, newTitle, accessKeyLabel,
+               separatorMsg = mw.message( 'word-separator' ).plain();
+
+       oldTitle = titleElement.title;
+       if ( !oldTitle ) {
+               // don't add a title if the element didn't have one before
+               return;
+       }
+
+       parts = ( separatorMsg + mw.message( 'brackets' ).plain() ).split( '$1' );
+       regexp = new RegExp( parts.map( mw.util.escapeRegExp ).join( '.*?' ) + '$' );
+       newTitle = oldTitle.replace( regexp, '' );
+       accessKeyLabel = getAccessKeyLabel( element );
+
+       if ( accessKeyLabel ) {
+               // Should be build the same as in Linker::titleAttrib
+               newTitle += separatorMsg + mw.message( 'brackets', accessKeyLabel ).plain();
+       }
+       if ( oldTitle !== newTitle ) {
+               titleElement.title = newTitle;
+       }
+}
+
+/**
+ * Update the title for an element to show the correct access key label.
+ *
+ * @private
+ * @param {HTMLElement} element Element with the accesskey
+ */
+function updateTooltip( element ) {
+       var id, $element, $label, $labelParent;
+       updateTooltipOnElement( element, element );
+
+       // update associated label if there is one
+       $element = $( element );
+       if ( $element.is( labelable ) ) {
+               // Search it using 'for' attribute
+               id = element.id.replace( /"/g, '\\"' );
+               if ( id ) {
+                       $label = $( 'label[for="' + id + '"]' );
+                       if ( $label.length === 1 ) {
+                               updateTooltipOnElement( element, $label[ 0 ] );
+                       }
+               }
+
+               // Search it as parent, because the form control can also be inside the label element itself
+               $labelParent = $element.parents( 'label' );
+               if ( $labelParent.length === 1 ) {
+                       updateTooltipOnElement( element, $labelParent[ 0 ] );
+               }
+       }
+}
+
+/**
+ * Update the titles for all elements in a jQuery selection.
+ *
+ * @return {jQuery}
+ * @chainable
+ */
+$.fn.updateTooltipAccessKeys = function () {
+       return this.each( function () {
+               updateTooltip( this );
+       } );
+};
+
+/**
+ * getAccessKeyModifiers
+ *
+ * @method updateTooltipAccessKeys_getAccessKeyModifiers
+ * @inheritdoc #getAccessKeyModifiers
+ */
+$.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers;
+
+/**
+ * getAccessKeyLabel
+ *
+ * @method updateTooltipAccessKeys_getAccessKeyLabel
+ * @inheritdoc #getAccessKeyLabel
+ */
+$.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel;
+
+/**
+ * getAccessKeyPrefix
+ *
+ * @method updateTooltipAccessKeys_getAccessKeyPrefix
+ * @deprecated since 1.27 Use #getAccessKeyModifiers
+ * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
+ * @return {string}
+ */
+$.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) {
+       return getAccessKeyModifiers( ua ).join( '-' ) + '-';
+};
+
+/**
+ * Switch test mode on and off.
+ *
+ * @method updateTooltipAccessKeys_setTestMode
+ * @param {boolean} mode New mode
+ */
+$.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) {
+       useTestPrefix = mode;
+};
+
+/**
+ * @class jQuery
+ * @mixins jQuery.plugin.accessKeyLabel
+ */
diff --git a/resources/src/mediawiki.util/util.js b/resources/src/mediawiki.util/util.js
new file mode 100644 (file)
index 0000000..e8823e1
--- /dev/null
@@ -0,0 +1,585 @@
+'use strict';
+
+var util,
+       config = require( './config.json' );
+
+require( './jquery.accessKeyLabel.js' );
+
+/**
+ * Encode the string like PHP's rawurlencode
+ * @ignore
+ *
+ * @param {string} str String to be encoded.
+ * @return {string} Encoded string
+ */
+function rawurlencode( str ) {
+       str = String( str );
+       return encodeURIComponent( str )
+               .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
+               .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
+}
+
+/**
+ * Private helper function used by util.escapeId*()
+ * @ignore
+ *
+ * @param {string} str String to be encoded
+ * @param {string} mode Encoding mode, see documentation for $wgFragmentMode
+ *     in DefaultSettings.php
+ * @return {string} Encoded string
+ */
+function escapeIdInternal( str, mode ) {
+       str = String( str );
+
+       switch ( mode ) {
+               case 'html5':
+                       return str.replace( / /g, '_' );
+               case 'legacy':
+                       return rawurlencode( str.replace( / /g, '_' ) )
+                               .replace( /%3A/g, ':' )
+                               .replace( /%/g, '.' );
+               default:
+                       throw new Error( 'Unrecognized ID escaping mode ' + mode );
+       }
+}
+
+/**
+ * Utility library
+ * @class mw.util
+ * @singleton
+ */
+util = {
+
+       /**
+        * Encode the string like PHP's rawurlencode
+        *
+        * @param {string} str String to be encoded.
+        * @return {string} Encoded string
+        */
+       rawurlencode: rawurlencode,
+
+       /**
+        * Encode string into HTML id compatible form suitable for use in HTML
+        * Analog to PHP Sanitizer::escapeIdForAttribute()
+        *
+        * @since 1.30
+        *
+        * @param {string} str String to encode
+        * @return {string} Encoded string
+        */
+       escapeIdForAttribute: function ( str ) {
+               var mode = config.FragmentMode[ 0 ];
+
+               return escapeIdInternal( str, mode );
+       },
+
+       /**
+        * Encode string into HTML id compatible form suitable for use in links
+        * Analog to PHP Sanitizer::escapeIdForLink()
+        *
+        * @since 1.30
+        *
+        * @param {string} str String to encode
+        * @return {string} Encoded string
+        */
+       escapeIdForLink: function ( str ) {
+               var mode = config.FragmentMode[ 0 ];
+
+               return escapeIdInternal( str, mode );
+       },
+
+       /**
+        * Encode page titles for use in a URL
+        *
+        * We want / and : to be included as literal characters in our title URLs
+        * as they otherwise fatally break the title.
+        *
+        * The others are decoded because we can, it's prettier and matches behaviour
+        * of `wfUrlencode` in PHP.
+        *
+        * @param {string} str String to be encoded.
+        * @return {string} Encoded string
+        */
+       wikiUrlencode: function ( str ) {
+               return util.rawurlencode( str )
+                       .replace( /%20/g, '_' )
+                       // wfUrlencode replacements
+                       .replace( /%3B/g, ';' )
+                       .replace( /%40/g, '@' )
+                       .replace( /%24/g, '$' )
+                       .replace( /%21/g, '!' )
+                       .replace( /%2A/g, '*' )
+                       .replace( /%28/g, '(' )
+                       .replace( /%29/g, ')' )
+                       .replace( /%2C/g, ',' )
+                       .replace( /%2F/g, '/' )
+                       .replace( /%7E/g, '~' )
+                       .replace( /%3A/g, ':' );
+       },
+
+       /**
+        * Get the link to a page name (relative to `wgServer`),
+        *
+        * @param {string|null} [pageName=wgPageName] Page name
+        * @param {Object} [params] A mapping of query parameter names to values,
+        *  e.g. `{ action: 'edit' }`
+        * @return {string} Url of the page with name of `pageName`
+        */
+       getUrl: function ( pageName, params ) {
+               var titleFragmentStart, url, query,
+                       fragment = '',
+                       title = typeof pageName === 'string' ? pageName : mw.config.get( 'wgPageName' );
+
+               // Find any fragment
+               titleFragmentStart = title.indexOf( '#' );
+               if ( titleFragmentStart !== -1 ) {
+                       fragment = title.slice( titleFragmentStart + 1 );
+                       // Exclude the fragment from the page name
+                       title = title.slice( 0, titleFragmentStart );
+               }
+
+               // Produce query string
+               if ( params ) {
+                       query = $.param( params );
+               }
+               if ( query ) {
+                       url = title ?
+                               util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query :
+                               util.wikiScript() + '?' + query;
+               } else {
+                       url = mw.config.get( 'wgArticlePath' )
+                               .replace( '$1', util.wikiUrlencode( title ).replace( /\$/g, '$$$$' ) );
+               }
+
+               // Append the encoded fragment
+               if ( fragment.length ) {
+                       url += '#' + util.escapeIdForLink( fragment );
+               }
+
+               return url;
+       },
+
+       /**
+        * Get address to a script in the wiki root.
+        * For index.php use `mw.config.get( 'wgScript' )`.
+        *
+        * @since 1.18
+        * @param {string} str Name of script (e.g. 'api'), defaults to 'index'
+        * @return {string} Address to script (e.g. '/w/api.php' )
+        */
+       wikiScript: function ( str ) {
+               str = str || 'index';
+               if ( str === 'index' ) {
+                       return mw.config.get( 'wgScript' );
+               } else if ( str === 'load' ) {
+                       return config.LoadScript;
+               } else {
+                       return mw.config.get( 'wgScriptPath' ) + '/' + str + '.php';
+               }
+       },
+
+       /**
+        * Append a new style block to the head and return the CSSStyleSheet object.
+        * Use .ownerNode to access the `<style>` element, or use mw.loader#addStyleTag.
+        * This function returns the styleSheet object for convience (due to cross-browsers
+        * difference as to where it is located).
+        *
+        *     var sheet = util.addCSS( '.foobar { display: none; }' );
+        *     $( foo ).click( function () {
+        *         // Toggle the sheet on and off
+        *         sheet.disabled = !sheet.disabled;
+        *     } );
+        *
+        * @param {string} text CSS to be appended
+        * @return {CSSStyleSheet} Use .ownerNode to get to the `<style>` element.
+        */
+       addCSS: function ( text ) {
+               var s = mw.loader.addStyleTag( text );
+               return s.sheet || s.styleSheet || s;
+       },
+
+       /**
+        * Grab the URL parameter value for the given parameter.
+        * Returns null if not found.
+        *
+        * @param {string} param The parameter name.
+        * @param {string} [url=location.href] URL to search through, defaulting to the current browsing location.
+        * @return {Mixed} Parameter value or null.
+        */
+       getParamValue: function ( param, url ) {
+               // Get last match, stop at hash
+               var re = new RegExp( '^[^#]*[&?]' + util.escapeRegExp( param ) + '=([^&#]*)' ),
+                       m = re.exec( url !== undefined ? url : location.href );
+
+               if ( m ) {
+                       // Beware that decodeURIComponent is not required to understand '+'
+                       // by spec, as encodeURIComponent does not produce it.
+                       return decodeURIComponent( m[ 1 ].replace( /\+/g, '%20' ) );
+               }
+               return null;
+       },
+
+       /**
+        * The content wrapper of the skin (e.g. `.mw-body`).
+        *
+        * Populated on document ready. To use this property,
+        * wait for `$.ready` and be sure to have a module dependency on
+        * `mediawiki.util` which will ensure
+        * your document ready handler fires after initialization.
+        *
+        * Because of the lazy-initialised nature of this property,
+        * you're discouraged from using it.
+        *
+        * If you need just the wikipage content (not any of the
+        * extra elements output by the skin), use `$( '#mw-content-text' )`
+        * instead. Or listen to mw.hook#wikipage_content which will
+        * allow your code to re-run when the page changes (e.g. live preview
+        * or re-render after ajax save).
+        *
+        * @property {jQuery}
+        */
+       $content: null,
+
+       /**
+        * Add a link to a portlet menu on the page, such as:
+        *
+        * p-cactions (Content actions), p-personal (Personal tools),
+        * p-navigation (Navigation), p-tb (Toolbox)
+        *
+        * The first three parameters are required, the others are optional and
+        * may be null. Though providing an id and tooltip is recommended.
+        *
+        * By default the new link will be added to the end of the list. To
+        * add the link before a given existing item, pass the DOM node
+        * (e.g. `document.getElementById( 'foobar' )`) or a jQuery-selector
+        * (e.g. `'#foobar'`) for that item.
+        *
+        *     util.addPortletLink(
+        *         'p-tb', 'https://www.mediawiki.org/',
+        *         'mediawiki.org', 't-mworg', 'Go to mediawiki.org', 'm', '#t-print'
+        *     );
+        *
+        *     var node = util.addPortletLink(
+        *         'p-tb',
+        *         new mw.Title( 'Special:Example' ).getUrl(),
+        *         'Example'
+        *     );
+        *     $( node ).on( 'click', function ( e ) {
+        *         console.log( 'Example' );
+        *         e.preventDefault();
+        *     } );
+        *
+        * @param {string} portletId ID of the target portlet (e.g. 'p-cactions' or 'p-personal')
+        * @param {string} href Link URL
+        * @param {string} text Link text
+        * @param {string} [id] ID of the list item, should be unique and preferably have
+        *  the appropriate prefix ('ca-', 'pt-', 'n-' or 't-')
+        * @param {string} [tooltip] Text to show when hovering over the link, without accesskey suffix
+        * @param {string} [accesskey] Access key to activate this link. One character only,
+        *  avoid conflicts with other links. Use `$( '[accesskey=x]' )` in the console to
+        *  see if 'x' is already used.
+        * @param {HTMLElement|jQuery|string} [nextnode] Element that the new item should be added before.
+        *  Must be another item in the same list, it will be ignored otherwise.
+        *  Can be specified as DOM reference, as jQuery object, or as CSS selector string.
+        * @return {HTMLElement|null} The added list item, or null if no element was added.
+        */
+       addPortletLink: function ( portletId, href, text, id, tooltip, accesskey, nextnode ) {
+               var item, link, $portlet, portlet, portletDiv, ul, next;
+
+               if ( !portletId ) {
+                       // Avoid confusing id="undefined" lookup
+                       return null;
+               }
+
+               portlet = document.getElementById( portletId );
+               if ( !portlet ) {
+                       // Invalid portlet ID
+                       return null;
+               }
+
+               // Setup the anchor tag and set any the properties
+               link = document.createElement( 'a' );
+               link.href = href;
+               link.textContent = text;
+               if ( tooltip ) {
+                       link.title = tooltip;
+               }
+               if ( accesskey ) {
+                       link.accessKey = accesskey;
+               }
+
+               // Unhide portlet if it was hidden before
+               $portlet = $( portlet );
+               $portlet.removeClass( 'emptyPortlet' );
+
+               // Setup the list item (and a span if $portlet is a Vector tab)
+               // eslint-disable-next-line no-jquery/no-class-state
+               if ( $portlet.hasClass( 'vectorTabs' ) ) {
+                       item = $( '<li>' ).append( $( '<span>' ).append( link )[ 0 ] )[ 0 ];
+               } else {
+                       item = $( '<li>' ).append( link )[ 0 ];
+               }
+               if ( id ) {
+                       item.id = id;
+               }
+
+               // Select the first (most likely only) unordered list inside the portlet
+               ul = portlet.querySelector( 'ul' );
+               if ( !ul ) {
+                       // If it didn't have an unordered list yet, create one
+                       ul = document.createElement( 'ul' );
+                       portletDiv = portlet.querySelector( 'div' );
+                       if ( portletDiv ) {
+                               // Support: Legacy skins have a div (such as div.body or div.pBody).
+                               // Append the <ul> to that.
+                               portletDiv.appendChild( ul );
+                       } else {
+                               // Append it to the portlet directly
+                               portlet.appendChild( ul );
+                       }
+               }
+
+               if ( nextnode && ( typeof nextnode === 'string' || nextnode.nodeType || nextnode.jquery ) ) {
+                       nextnode = $( ul ).find( nextnode );
+                       if ( nextnode.length === 1 && nextnode[ 0 ].parentNode === ul ) {
+                               // Insertion point: Before nextnode
+                               nextnode.before( item );
+                               next = true;
+                       }
+                       // Else: Invalid nextnode value (no match, more than one match, or not a direct child)
+                       // Else: Invalid nextnode type
+               }
+
+               if ( !next ) {
+                       // Insertion point: End of list (default)
+                       ul.appendChild( item );
+               }
+
+               // Update tooltip for the access key after inserting into DOM
+               // to get a localized access key label (T69946).
+               if ( accesskey ) {
+                       $( link ).updateTooltipAccessKeys();
+               }
+
+               return item;
+       },
+
+       /**
+        * Validate a string as representing a valid e-mail address
+        * according to HTML5 specification. Please note the specification
+        * does not validate a domain with one character.
+        *
+        * FIXME: should be moved to or replaced by a validation module.
+        *
+        * @param {string} mailtxt E-mail address to be validated.
+        * @return {boolean|null} Null if `mailtxt` was an empty string, otherwise true/false
+        * as determined by validation.
+        */
+       validateEmail: function ( mailtxt ) {
+               var rfc5322Atext, rfc1034LdhStr, html5EmailRegexp;
+
+               if ( mailtxt === '' ) {
+                       return null;
+               }
+
+               // HTML5 defines a string as valid e-mail address if it matches
+               // the ABNF:
+               //     1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str )
+               // With:
+               // - atext   : defined in RFC 5322 section 3.2.3
+               // - ldh-str : defined in RFC 1034 section 3.5
+               //
+               // (see STD 68 / RFC 5234 https://tools.ietf.org/html/std68)
+               // First, define the RFC 5322 'atext' which is pretty easy:
+               // atext = ALPHA / DIGIT / ; Printable US-ASCII
+               //     "!" / "#" /    ; characters not including
+               //     "$" / "%" /    ; specials. Used for atoms.
+               //     "&" / "'" /
+               //     "*" / "+" /
+               //     "-" / "/" /
+               //     "=" / "?" /
+               //     "^" / "_" /
+               //     "`" / "{" /
+               //     "|" / "}" /
+               //     "~"
+               rfc5322Atext = 'a-z0-9!#$%&\'*+\\-/=?^_`{|}~';
+
+               // Next define the RFC 1034 'ldh-str'
+               //     <domain> ::= <subdomain> | " "
+               //     <subdomain> ::= <label> | <subdomain> "." <label>
+               //     <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+               //     <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+               //     <let-dig-hyp> ::= <let-dig> | "-"
+               //     <let-dig> ::= <letter> | <digit>
+               rfc1034LdhStr = 'a-z0-9\\-';
+
+               html5EmailRegexp = new RegExp(
+                       // start of string
+                       '^' +
+                       // User part which is liberal :p
+                       '[' + rfc5322Atext + '\\.]+' +
+                       // 'at'
+                       '@' +
+                       // Domain first part
+                       '[' + rfc1034LdhStr + ']+' +
+                       // Optional second part and following are separated by a dot
+                       '(?:\\.[' + rfc1034LdhStr + ']+)*' +
+                       // End of string
+                       '$',
+                       // RegExp is case insensitive
+                       'i'
+               );
+               return ( mailtxt.match( html5EmailRegexp ) !== null );
+       },
+
+       /**
+        * Note: borrows from IP::isIPv4
+        *
+        * @param {string} address
+        * @param {boolean} [allowBlock=false]
+        * @return {boolean}
+        */
+       isIPv4Address: function ( address, allowBlock ) {
+               var block, RE_IP_BYTE, RE_IP_ADD;
+
+               if ( typeof address !== 'string' ) {
+                       return false;
+               }
+
+               block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '';
+               RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])';
+               RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE;
+
+               return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) );
+       },
+
+       /**
+        * Note: borrows from IP::isIPv6
+        *
+        * @param {string} address
+        * @param {boolean} [allowBlock=false]
+        * @return {boolean}
+        */
+       isIPv6Address: function ( address, allowBlock ) {
+               var block, RE_IPV6_ADD;
+
+               if ( typeof address !== 'string' ) {
+                       return false;
+               }
+
+               block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '';
+               RE_IPV6_ADD =
+                       '(?:' + // starts with "::" (including "::")
+                               ':(?::|(?::' +
+                                       '[0-9A-Fa-f]{1,4}' +
+                               '){1,7})' +
+                               '|' + // ends with "::" (except "::")
+                               '[0-9A-Fa-f]{1,4}' +
+                               '(?::' +
+                                       '[0-9A-Fa-f]{1,4}' +
+                               '){0,6}::' +
+                               '|' + // contains no "::"
+                               '[0-9A-Fa-f]{1,4}' +
+                               '(?::' +
+                                       '[0-9A-Fa-f]{1,4}' +
+                               '){7}' +
+                       ')';
+
+               if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) {
+                       return true;
+               }
+
+               // contains one "::" in the middle (single '::' check below)
+               RE_IPV6_ADD =
+                       '[0-9A-Fa-f]{1,4}' +
+                       '(?:::?' +
+                               '[0-9A-Fa-f]{1,4}' +
+                       '){1,6}';
+
+               return (
+                       new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) &&
+                       /::/.test( address ) &&
+                       !/::.*::/.test( address )
+               );
+       },
+
+       /**
+        * Check whether a string is an IP address
+        *
+        * @since 1.25
+        * @param {string} address String to check
+        * @param {boolean} [allowBlock=false] If a block of IPs should be allowed
+        * @return {boolean}
+        */
+       isIPAddress: function ( address, allowBlock ) {
+               return util.isIPv4Address( address, allowBlock ) ||
+                       util.isIPv6Address( address, allowBlock );
+       },
+
+       /**
+        * Escape string for safe inclusion in regular expression
+        *
+        * The following characters are escaped:
+        *
+        *     \ { } ( ) | . ? * + - ^ $ [ ]
+        *
+        * @since 1.26; moved to mw.util in 1.34
+        * @param {string} str String to escape
+        * @return {string} Escaped string
+        */
+       escapeRegExp: function ( str ) {
+               // eslint-disable-next-line no-useless-escape
+               return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' );
+       }
+};
+
+// Backwards-compatible alias for mediawiki.RegExp module.
+// @deprecated since 1.34
+mw.RegExp = {};
+mw.log.deprecate( mw.RegExp, 'escape', util.escapeRegExp, 'Use mw.util.escapeRegExp() instead.', 'mw.RegExp.escape' );
+
+// Not allowed outside unit tests
+if ( window.QUnit ) {
+       util.setOptionsForTest = function ( opts ) {
+               var oldConfig = config;
+               config = $.extend( {}, config, opts );
+               return oldConfig;
+       };
+}
+
+/**
+ * Initialisation of mw.util.$content
+ */
+function init() {
+       util.$content = ( function () {
+               var i, l, $node, selectors;
+
+               selectors = [
+                       // The preferred standard is class "mw-body".
+                       // You may also use class "mw-body mw-body-primary" if you use
+                       // mw-body in multiple locations. Or class "mw-body-primary" if
+                       // you use mw-body deeper in the DOM.
+                       '.mw-body-primary',
+                       '.mw-body',
+
+                       // If the skin has no such class, fall back to the parser output
+                       '#mw-content-text'
+               ];
+
+               for ( i = 0, l = selectors.length; i < l; i++ ) {
+                       $node = $( selectors[ i ] );
+                       if ( $node.length ) {
+                               return $node.first();
+                       }
+               }
+
+               // Should never happen... well, it could if someone is not finished writing a
+               // skin and has not yet inserted bodytext yet.
+               return $( 'body' );
+       }() );
+}
+
+$( init );
+
+mw.util = util;
+module.exports = util;
index 81cf433..7102b67 100644 (file)
                        // eslint-disable-next-line no-restricted-properties
                        v = v.normalize();
                }
-               re = new RegExp( '^\\s*' + mw.RegExp.escape( v ), 'i' );
+               re = new RegExp( '^\\s*' + mw.util.escapeRegExp( v ), 'i' );
                for ( k in this.values ) {
                        k = +k;
                        if ( !isNaN( k ) && re.test( this.values[ k ] ) ) {
index 04658b9..d00ed82 100644 (file)
@@ -10,7 +10,7 @@
                        if ( mw.config.get( 'wgTranslateNumerals' ) ) {
                                for ( i = 0; i < 10; i++ ) {
                                        if ( table[ i ] !== undefined ) {
-                                               s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
+                                               s = s.replace( new RegExp( mw.util.escapeRegExp( table[ i ] ), 'g' ), i );
                                        }
                                }
                        }
index 3ac532e..f647f91 100644 (file)
--- a/rest.php
+++ b/rest.php
@@ -23,6 +23,9 @@
 
 use MediaWiki\Rest\EntryPoint;
 
+define( 'MW_REST_API', true );
+define( 'MW_ENTRY_POINT', 'rest' );
+
 require __DIR__ . '/includes/WebStart.php';
 
 EntryPoint::main();
index c8b8ef9..91c3d40 100644 (file)
@@ -1246,8 +1246,6 @@ class ParserTestRunner {
         * @return array
         */
        private function listTables() {
-               global $wgActorTableSchemaMigrationStage;
-
                $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
                        'protected_titles', 'revision', 'ip_changes', 'text', 'pagelinks', 'imagelinks',
                        'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
@@ -1256,15 +1254,9 @@ class ParserTestRunner {
                        'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
                        'archive', 'user_groups', 'page_props', 'category',
                        'slots', 'content', 'slot_roles', 'content_models',
-                       'comment', 'revision_comment_temp',
+                       'comment', 'revision_comment_temp', 'actor', 'revision_actor_temp',
                ];
 
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       // The new tables for actors are in use
-                       $tables[] = 'actor';
-                       $tables[] = 'revision_actor_temp';
-               }
-
                if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite' ] ) ) {
                        array_push( $tables, 'searchindex' );
                }
index 3f876ae..fda986c 100644 (file)
@@ -20,6 +20,7 @@
  */
 
 use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Exception;
 
 /**
  * Base class for unit tests.
@@ -37,6 +38,25 @@ abstract class MediaWikiUnitTestCase extends TestCase {
        private static $originalGlobals;
        private static $unitGlobals;
 
+       /**
+        * Whitelist of globals to allow in MediaWikiUnitTestCase.
+        *
+        * Please, keep this list to the bare minimum.
+        *
+        * @return string[]
+        */
+       private static function getGlobalsWhitelist() {
+               return [
+                       // The autoloader may change between bootstrap and the first test,
+                       // so (lazily) capture these here instead.
+                       'wgAutoloadClasses',
+                       'wgAutoloadLocalClasses',
+                       // Need for LoggerFactory. Default is NullSpi.
+                       'wgMWLoggerDefaultSpi',
+                       'wgAutoloadAttemptLowercase'
+               ];
+       }
+
        public static function setUpBeforeClass() {
                parent::setUpBeforeClass();
 
@@ -56,12 +76,10 @@ abstract class MediaWikiUnitTestCase extends TestCase {
                }
 
                self::$unitGlobals =& TestSetup::$bootstrapGlobals;
-               // The autoloader may change between bootstrap and the first test,
-               // so (lazily) capture these here instead.
-               self::$unitGlobals['wgAutoloadClasses'] =& $GLOBALS['wgAutoloadClasses'];
-               self::$unitGlobals['wgAutoloadLocalClasses'] =& $GLOBALS['wgAutoloadLocalClasses'];
-               // This value should always be true.
-               self::$unitGlobals['wgAutoloadAttemptLowercase'] = true;
+
+               foreach ( self::getGlobalsWhitelist() as $global ) {
+                       self::$unitGlobals[ $global ] =& $GLOBALS[ $global ];
+               }
 
                // Would be nice if we coud simply replace $GLOBALS as a whole,
                // but unsetting or re-assigning that breaks the reference of this magic
@@ -84,6 +102,22 @@ abstract class MediaWikiUnitTestCase extends TestCase {
                }
        }
 
+       /**
+        * @inheritDoc
+        */
+       protected function runTest() {
+               try {
+                       return parent::runTest();
+               } catch ( ConfigException $exception ) {
+                       throw new Exception(
+                               'Config variables must be mocked, they cannot be accessed directly in tests which extend '
+                               . self::class,
+                               $exception->getCode(),
+                               $exception
+                       );
+               }
+       }
+
        protected function tearDown() {
                if ( !defined( 'HHVM_VERSION' ) ) {
                        // Quick reset between tests
index 46c0c42..a82b697 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 use MediaWiki\User\UserIdentity;
-use MediaWiki\MediaWikiServices;
+use Wikimedia\ScopedCallback;
 use Wikimedia\TestingAccessWrapper;
 
 /**
@@ -10,14 +10,60 @@ use Wikimedia\TestingAccessWrapper;
  */
 class ActorMigrationTest extends MediaWikiLangTestCase {
 
+       protected $resetActorMigration = null;
+       protected static $amId = 0;
+
        protected $tablesUsed = [
-               'revision',
-               'revision_actor_temp',
-               'ipblocks',
-               'recentchanges',
                'actor',
        ];
 
+       protected function setUp() {
+               parent::setUp();
+
+               $w = TestingAccessWrapper::newFromClass( ActorMigration::class );
+               $data = [
+                       'tempTables' => $w->tempTables,
+                       'formerTempTables' => $w->formerTempTables,
+                       'deprecated' => $w->deprecated,
+                       'removed' => $w->removed,
+                       'specialFields' => $w->specialFields,
+               ];
+               $this->resetActorMigration = new ScopedCallback( function ( $w, $data ) {
+                       foreach ( $data as $k => $v ) {
+                               $w->$k = $v;
+                       }
+               }, [ $w, $data ] );
+
+               $w->tempTables = [
+                       'am2_user' => [
+                               'table' => 'actormigration2_temp',
+                               'pk' => 'am2t_id',
+                               'field' => 'am2t_actor',
+                               'joinPK' => 'am2_id',
+                               'extra' => [],
+                       ]
+               ];
+               $w->specialFields = [
+                       'am3_xxx' => [ 'am3_xxx_text', 'am3_xxx_actor' ],
+               ];
+       }
+
+       protected function tearDown() {
+               parent::tearDown();
+               ScopedCallback::consume( $this->resetActorMigration );
+       }
+
+       protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+               return [
+                       'scripts' => [
+                               __DIR__ . '/ActorMigrationTest.sql',
+                       ],
+                       'drop' => [],
+                       'create' => [ 'actormigration1', 'actormigration2', 'actormigration2_temp', 'actormigration3' ],
+                       'alter' => [],
+               ];
+       }
+
        /**
         * @dataProvider provideConstructor
         * @param int $stage
@@ -80,156 +126,156 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
        public static function provideGetJoin() {
                return [
                        'Simple table, old' => [
-                               SCHEMA_COMPAT_OLD, 'rc_user', [
+                               SCHEMA_COMPAT_OLD, 'am1_user', [
                                        'tables' => [],
                                        'fields' => [
-                                               'rc_user' => 'rc_user',
-                                               'rc_user_text' => 'rc_user_text',
-                                               'rc_actor' => 'NULL',
+                                               'am1_user' => 'am1_user',
+                                               'am1_user_text' => 'am1_user_text',
+                                               'am1_actor' => 'NULL',
                                        ],
                                        'joins' => [],
                                ],
                        ],
                        'Simple table, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', [
                                        'tables' => [],
                                        'fields' => [
-                                               'rc_user' => 'rc_user',
-                                               'rc_user_text' => 'rc_user_text',
-                                               'rc_actor' => 'NULL',
+                                               'am1_user' => 'am1_user',
+                                               'am1_user_text' => 'am1_user_text',
+                                               'am1_actor' => 'NULL',
                                        ],
                                        'joins' => [],
                                ],
                        ],
                        'Simple table, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', [
-                                       'tables' => [ 'actor_rc_user' => 'actor' ],
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', [
+                                       'tables' => [ 'actor_am1_user' => 'actor' ],
                                        'fields' => [
-                                               'rc_user' => 'actor_rc_user.actor_user',
-                                               'rc_user_text' => 'actor_rc_user.actor_name',
-                                               'rc_actor' => 'rc_actor',
+                                               'am1_user' => 'actor_am1_user.actor_user',
+                                               'am1_user_text' => 'actor_am1_user.actor_name',
+                                               'am1_actor' => 'am1_actor',
                                        ],
                                        'joins' => [
-                                               'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                               'actor_am1_user' => [ 'JOIN', 'actor_am1_user.actor_id = am1_actor' ],
                                        ],
                                ],
                        ],
                        'Simple table, new' => [
-                               SCHEMA_COMPAT_NEW, 'rc_user', [
-                                       'tables' => [ 'actor_rc_user' => 'actor' ],
+                               SCHEMA_COMPAT_NEW, 'am1_user', [
+                                       'tables' => [ 'actor_am1_user' => 'actor' ],
                                        'fields' => [
-                                               'rc_user' => 'actor_rc_user.actor_user',
-                                               'rc_user_text' => 'actor_rc_user.actor_name',
-                                               'rc_actor' => 'rc_actor',
+                                               'am1_user' => 'actor_am1_user.actor_user',
+                                               'am1_user_text' => 'actor_am1_user.actor_name',
+                                               'am1_actor' => 'am1_actor',
                                        ],
                                        'joins' => [
-                                               'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                               'actor_am1_user' => [ 'JOIN', 'actor_am1_user.actor_id = am1_actor' ],
                                        ],
                                ],
                        ],
 
-                       'ipblocks, old' => [
-                               SCHEMA_COMPAT_OLD, 'ipb_by', [
+                       'Special name, old' => [
+                               SCHEMA_COMPAT_OLD, 'am3_xxx', [
                                        'tables' => [],
                                        'fields' => [
-                                               'ipb_by' => 'ipb_by',
-                                               'ipb_by_text' => 'ipb_by_text',
-                                               'ipb_by_actor' => 'NULL',
+                                               'am3_xxx' => 'am3_xxx',
+                                               'am3_xxx_text' => 'am3_xxx_text',
+                                               'am3_xxx_actor' => 'NULL',
                                        ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', [
+                       'Special name, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am3_xxx', [
                                        'tables' => [],
                                        'fields' => [
-                                               'ipb_by' => 'ipb_by',
-                                               'ipb_by_text' => 'ipb_by_text',
-                                               'ipb_by_actor' => 'NULL',
+                                               'am3_xxx' => 'am3_xxx',
+                                               'am3_xxx_text' => 'am3_xxx_text',
+                                               'am3_xxx_actor' => 'NULL',
                                        ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', [
-                                       'tables' => [ 'actor_ipb_by' => 'actor' ],
+                       'Special name, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am3_xxx', [
+                                       'tables' => [ 'actor_am3_xxx' => 'actor' ],
                                        'fields' => [
-                                               'ipb_by' => 'actor_ipb_by.actor_user',
-                                               'ipb_by_text' => 'actor_ipb_by.actor_name',
-                                               'ipb_by_actor' => 'ipb_by_actor',
+                                               'am3_xxx' => 'actor_am3_xxx.actor_user',
+                                               'am3_xxx_text' => 'actor_am3_xxx.actor_name',
+                                               'am3_xxx_actor' => 'am3_xxx_actor',
                                        ],
                                        'joins' => [
-                                               'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                               'actor_am3_xxx' => [ 'JOIN', 'actor_am3_xxx.actor_id = am3_xxx_actor' ],
                                        ],
                                ],
                        ],
-                       'ipblocks, new' => [
-                               SCHEMA_COMPAT_NEW, 'ipb_by', [
-                                       'tables' => [ 'actor_ipb_by' => 'actor' ],
+                       'Special name, new' => [
+                               SCHEMA_COMPAT_NEW, 'am3_xxx', [
+                                       'tables' => [ 'actor_am3_xxx' => 'actor' ],
                                        'fields' => [
-                                               'ipb_by' => 'actor_ipb_by.actor_user',
-                                               'ipb_by_text' => 'actor_ipb_by.actor_name',
-                                               'ipb_by_actor' => 'ipb_by_actor',
+                                               'am3_xxx' => 'actor_am3_xxx.actor_user',
+                                               'am3_xxx_text' => 'actor_am3_xxx.actor_name',
+                                               'am3_xxx_actor' => 'am3_xxx_actor',
                                        ],
                                        'joins' => [
-                                               'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                               'actor_am3_xxx' => [ 'JOIN', 'actor_am3_xxx.actor_id = am3_xxx_actor' ],
                                        ],
                                ],
                        ],
 
-                       'Revision, old' => [
-                               SCHEMA_COMPAT_OLD, 'rev_user', [
+                       'Temp table, old' => [
+                               SCHEMA_COMPAT_OLD, 'am2_user', [
                                        'tables' => [],
                                        'fields' => [
-                                               'rev_user' => 'rev_user',
-                                               'rev_user_text' => 'rev_user_text',
-                                               'rev_actor' => 'NULL',
+                                               'am2_user' => 'am2_user',
+                                               'am2_user_text' => 'am2_user_text',
+                                               'am2_actor' => 'NULL',
                                        ],
                                        'joins' => [],
                                ],
                        ],
-                       'Revision, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', [
+                       'Temp table, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am2_user', [
                                        'tables' => [],
                                        'fields' => [
-                                               'rev_user' => 'rev_user',
-                                               'rev_user_text' => 'rev_user_text',
-                                               'rev_actor' => 'NULL',
+                                               'am2_user' => 'am2_user',
+                                               'am2_user_text' => 'am2_user_text',
+                                               'am2_actor' => 'NULL',
                                        ],
                                        'joins' => [],
                                ],
                        ],
-                       'Revision, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', [
+                       'Temp table, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am2_user', [
                                        'tables' => [
-                                               'temp_rev_user' => 'revision_actor_temp',
-                                               'actor_rev_user' => 'actor',
+                                               'temp_am2_user' => 'actormigration2_temp',
+                                               'actor_am2_user' => 'actor',
                                        ],
                                        'fields' => [
-                                               'rev_user' => 'actor_rev_user.actor_user',
-                                               'rev_user_text' => 'actor_rev_user.actor_name',
-                                               'rev_actor' => 'temp_rev_user.revactor_actor',
+                                               'am2_user' => 'actor_am2_user.actor_user',
+                                               'am2_user_text' => 'actor_am2_user.actor_name',
+                                               'am2_actor' => 'temp_am2_user.am2t_actor',
                                        ],
                                        'joins' => [
-                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
-                                               'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                               'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
+                                               'actor_am2_user' => [ 'JOIN', 'actor_am2_user.actor_id = temp_am2_user.am2t_actor' ],
                                        ],
                                ],
                        ],
-                       'Revision, new' => [
-                               SCHEMA_COMPAT_NEW, 'rev_user', [
+                       'Temp table, new' => [
+                               SCHEMA_COMPAT_NEW, 'am2_user', [
                                        'tables' => [
-                                               'temp_rev_user' => 'revision_actor_temp',
-                                               'actor_rev_user' => 'actor',
+                                               'temp_am2_user' => 'actormigration2_temp',
+                                               'actor_am2_user' => 'actor',
                                        ],
                                        'fields' => [
-                                               'rev_user' => 'actor_rev_user.actor_user',
-                                               'rev_user_text' => 'actor_rev_user.actor_name',
-                                               'rev_actor' => 'temp_rev_user.revactor_actor',
+                                               'am2_user' => 'actor_am2_user.actor_user',
+                                               'am2_user_text' => 'actor_am2_user.actor_name',
+                                               'am2_actor' => 'temp_am2_user.am2t_actor',
                                        ],
                                        'joins' => [
-                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
-                                               'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                               'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
+                                               'actor_am2_user' => [ 'JOIN', 'actor_am2_user.actor_id = temp_am2_user.am2t_actor' ],
                                        ],
                                ],
                        ],
@@ -276,164 +322,164 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
 
                return [
                        'Simple table, old' => [
-                               SCHEMA_COMPAT_OLD, 'rc_user', $genericUser, true, [
+                               SCHEMA_COMPAT_OLD, 'am1_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'userid' => "rc_user = '1'" ],
+                                       'orconds' => [ 'userid' => "am1_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
                        'Simple table, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $genericUser, true, [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'userid' => "rc_user = '1'" ],
+                                       'orconds' => [ 'userid' => "am1_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
                        'Simple table, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $genericUser, true, [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "rc_actor = '11'" ],
+                                       'orconds' => [ 'actor' => "am1_actor = '11'" ],
                                        'joins' => [],
                                ],
                        ],
                        'Simple table, new' => [
-                               SCHEMA_COMPAT_NEW, 'rc_user', $genericUser, true, [
+                               SCHEMA_COMPAT_NEW, 'am1_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "rc_actor = '11'" ],
+                                       'orconds' => [ 'actor' => "am1_actor = '11'" ],
                                        'joins' => [],
                                ],
                        ],
 
-                       'ipblocks, old' => [
-                               SCHEMA_COMPAT_OLD, 'ipb_by', $genericUser, true, [
+                       'Special name, old' => [
+                               SCHEMA_COMPAT_OLD, 'am3_xxx', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'userid' => "ipb_by = '1'" ],
+                                       'orconds' => [ 'userid' => "am3_xxx = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', $genericUser, true, [
+                       'Special name, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am3_xxx', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'userid' => "ipb_by = '1'" ],
+                                       'orconds' => [ 'userid' => "am3_xxx = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', $genericUser, true, [
+                       'Special name, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am3_xxx', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
+                                       'orconds' => [ 'actor' => "am3_xxx_actor = '11'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'ipblocks, new' => [
-                               SCHEMA_COMPAT_NEW, 'ipb_by', $genericUser, true, [
+                       'Special name, new' => [
+                               SCHEMA_COMPAT_NEW, 'am3_xxx', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
+                                       'orconds' => [ 'actor' => "am3_xxx_actor = '11'" ],
                                        'joins' => [],
                                ],
                        ],
 
-                       'Revision, old' => [
-                               SCHEMA_COMPAT_OLD, 'rev_user', $genericUser, true, [
+                       'Temp table, old' => [
+                               SCHEMA_COMPAT_OLD, 'am2_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'userid' => "rev_user = '1'" ],
+                                       'orconds' => [ 'userid' => "am2_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'Revision, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', $genericUser, true, [
+                       'Temp table, read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am2_user', $genericUser, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'userid' => "rev_user = '1'" ],
+                                       'orconds' => [ 'userid' => "am2_user = '1'" ],
                                        'joins' => [],
                                ],
                        ],
-                       'Revision, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', $genericUser, true, [
+                       'Temp table, read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am2_user', $genericUser, true, [
                                        'tables' => [
-                                               'temp_rev_user' => 'revision_actor_temp',
+                                               'temp_am2_user' => 'actormigration2_temp',
                                        ],
-                                       'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
+                                       'orconds' => [ 'actor' => "temp_am2_user.am2t_actor = '11'" ],
                                        'joins' => [
-                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
                                        ],
                                ],
                        ],
-                       'Revision, new' => [
-                               SCHEMA_COMPAT_NEW, 'rev_user', $genericUser, true, [
+                       'Temp table, new' => [
+                               SCHEMA_COMPAT_NEW, 'am2_user', $genericUser, true, [
                                        'tables' => [
-                                               'temp_rev_user' => 'revision_actor_temp',
+                                               'temp_am2_user' => 'actormigration2_temp',
                                        ],
-                                       'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
+                                       'orconds' => [ 'actor' => "temp_am2_user.am2t_actor = '11'" ],
                                        'joins' => [
-                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'temp_am2_user' => [ 'JOIN', 'temp_am2_user.am2t_id = am2_id' ],
                                        ],
                                ],
                        ],
 
                        'Multiple users, old' => [
-                               SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, true, [
+                               SCHEMA_COMPAT_OLD, 'am1_user', $complicatedUsers, true, [
                                        'tables' => [],
                                        'orconds' => [
-                                               'userid' => "rc_user IN ('1','2','3') ",
-                                               'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
+                                               'userid' => "am1_user IN ('1','2','3') ",
+                                               'username' => "am1_user_text IN ('192.168.12.34','192.168.12.35') "
                                        ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, true, [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', $complicatedUsers, true, [
                                        'tables' => [],
                                        'orconds' => [
-                                               'userid' => "rc_user IN ('1','2','3') ",
-                                               'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
+                                               'userid' => "am1_user IN ('1','2','3') ",
+                                               'username' => "am1_user_text IN ('192.168.12.34','192.168.12.35') "
                                        ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, true, [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', $complicatedUsers, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+                                       'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, new' => [
-                               SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, true, [
+                               SCHEMA_COMPAT_NEW, 'am1_user', $complicatedUsers, true, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+                                       'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
                                        'joins' => [],
                                ],
                        ],
 
                        'Multiple users, no use ID, old' => [
-                               SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, false, [
+                               SCHEMA_COMPAT_OLD, 'am1_user', $complicatedUsers, false, [
                                        'tables' => [],
                                        'orconds' => [
-                                               'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+                                               'username' => "am1_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
                                        ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, read-old' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, false, [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'am1_user', $complicatedUsers, false, [
                                        'tables' => [],
                                        'orconds' => [
-                                               'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+                                               'username' => "am1_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
                                        ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, read-new' => [
-                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, false, [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'am1_user', $complicatedUsers, false, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+                                       'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
                                        'joins' => [],
                                ],
                        ],
                        'Multiple users, new' => [
-                               SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, false, [
+                               SCHEMA_COMPAT_NEW, 'am1_user', $complicatedUsers, false, [
                                        'tables' => [],
-                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+                                       'orconds' => [ 'actor' => "am1_actor IN ('11','12','34') " ],
                                        'joins' => [],
                                ],
                        ],
@@ -445,23 +491,14 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
         * @param string $table
         * @param string $key
         * @param string $pk
-        * @param array $extraFields
+        * @param bool $usesTemp
         */
-       public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
+       public function testInsertRoundTrip( $table, $key, $pk, $usesTemp ) {
                $u = $this->getTestUser()->getUser();
                $user = $this->getMock( UserIdentity::class );
                $user->method( 'getId' )->willReturn( $u->getId() );
                $user->method( 'getName' )->willReturn( $u->getName() );
-               if ( $u->getActorId( $this->db ) ) {
-                       $user->method( 'getActorId' )->willReturn( $u->getActorId() );
-               } else {
-                       $this->db->insert(
-                               'actor',
-                               [ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
-                               __METHOD__
-                       );
-                       $user->method( 'getActorId' )->willReturn( $this->db->insertId() );
-               }
+               $user->method( 'getActorId' )->willReturn( $u->getActorId( $this->db ) );
 
                $stageNames = [
                        SCHEMA_COMPAT_OLD => 'old',
@@ -494,15 +531,10 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                ];
 
                $nameKey = $key . '_text';
-               $actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
+               $actorKey = ( $key === 'am3_xxx' ? $key : substr( $key, 0, -5 ) ) . '_actor';
 
                foreach ( $stages as $writeStage => $possibleReadStages ) {
-                       if ( $key === 'ipb_by' ) {
-                               $extraFields['ipb_address'] = __CLASS__ . "#{$stageNames[$writeStage]}";
-                       }
-
                        $w = new ActorMigration( $writeStage );
-                       $usesTemp = $key === 'rev_user';
 
                        if ( $usesTemp ) {
                                list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db, $key, $user );
@@ -527,10 +559,10 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                                        "new field, stage={$stageNames[$writeStage]}" );
                        }
 
-                       $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
-                       $id = $this->db->insertId();
+                       $id = ++self::$amId;
+                       $this->db->insert( $table, [ $pk => $id ] + $fields, __METHOD__ );
                        if ( $usesTemp ) {
-                               $callback( $id, $extraFields );
+                               $callback( $id, [] );
                        }
 
                        foreach ( $possibleReadStages as $readStage ) {
@@ -560,33 +592,10 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
        }
 
        public static function provideInsertRoundTrip() {
-               $db = wfGetDB( DB_REPLICA ); // for timestamps
-
-               $comment = MediaWikiServices::getInstance()->getCommentStore()
-                       ->createComment( wfGetDB( DB_MASTER ), '' );
-
                return [
-                       'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
-                               'rc_timestamp' => $db->timestamp(),
-                               'rc_namespace' => 0,
-                               'rc_title' => 'Test',
-                               'rc_this_oldid' => 42,
-                               'rc_last_oldid' => 41,
-                               'rc_source' => 'test',
-                               'rc_comment_id' => $comment->id,
-                       ] ],
-                       'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
-                               'ipb_range_start' => '',
-                               'ipb_range_end' => '',
-                               'ipb_timestamp' => $db->timestamp(),
-                               'ipb_expiry' => $db->getInfinity(),
-                               'ipb_reason_id' => $comment->id,
-                       ] ],
-                       'revision' => [ 'revision', 'rev_user', 'rev_id', [
-                               'rev_page' => 42,
-                               'rev_len' => 0,
-                               'rev_timestamp' => $db->timestamp(),
-                       ] ],
+                       'normal' => [ 'actormigration1', 'am1_user', 'am1_id', false ],
+                       'temp table' => [ 'actormigration2', 'am2_user', 'am2_id', true ],
+                       'special name' => [ 'actormigration3', 'am3_xxx', 'am3_id', false ],
                ];
        }
 
@@ -603,22 +612,22 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
         * @dataProvider provideStages
         * @param int $stage
         * @expectedException InvalidArgumentException
-        * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
+        * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for am2_user
         */
        public function testInsertWrong( $stage ) {
                $m = new ActorMigration( $stage );
-               $m->getInsertValues( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+               $m->getInsertValues( $this->db, 'am2_user', $this->getTestUser()->getUser() );
        }
 
        /**
         * @dataProvider provideStages
         * @param int $stage
         * @expectedException InvalidArgumentException
-        * @expectedExceptionMessage Must use getInsertValues() for rc_user
+        * @expectedExceptionMessage Must use getInsertValues() for am1_user
         */
        public function testInsertWithTempTableWrong( $stage ) {
                $m = new ActorMigration( $stage );
-               $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+               $m->getInsertValuesWithTempTable( $this->db, 'am1_user', $this->getTestUser()->getUser() );
        }
 
        /**
@@ -627,12 +636,12 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
         */
        public function testInsertWithTempTableDeprecated( $stage ) {
                $wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
-               $wrap->formerTempTables += [ 'rc_user' => '1.30' ];
+               $wrap->formerTempTables += [ 'am1_user' => '1.30' ];
 
-               $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
+               $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for am1_user' );
                $m = new ActorMigration( $stage );
                list( $fields, $callback )
-                       = $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+                       = $m->getInsertValuesWithTempTable( $this->db, 'am1_user', $this->getTestUser()->getUser() );
                $this->assertTrue( is_callable( $callback ) );
        }
 
@@ -640,12 +649,23 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
         * @dataProvider provideStages
         * @param int $stage
         * @expectedException InvalidArgumentException
-        * @expectedExceptionMessage $extra[rev_timestamp] is not provided
+        * @expectedExceptionMessage $extra[foo_timestamp] is not provided
         */
        public function testInsertWithTempTableCallbackMissingFields( $stage ) {
+               $w = TestingAccessWrapper::newFromClass( ActorMigration::class );
+               $w->tempTables = [
+                       'foo_user' => [
+                               'table' => 'foo_temp',
+                               'pk' => 'footmp_id',
+                               'field' => 'footmp_actor',
+                               'joinPK' => 'foo_id',
+                               'extra' => [ 'footmp_timestamp' => 'foo_timestamp' ],
+                       ],
+               ];
+
                $m = new ActorMigration( $stage );
                list( $fields, $callback )
-                       = $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+                       = $m->getInsertValuesWithTempTable( $this->db, 'foo_user', $this->getTestUser()->getUser() );
                $callback( 1, [] );
        }
 
@@ -654,41 +674,33 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
         * @param int $stage
         */
        public function testInsertUserIdentity( $stage ) {
-               $this->setMwGlobals( [
-                       // for User::getActorId()
-                       'wgActorTableSchemaMigrationStage' => $stage
-               ] );
-
                $user = $this->getMutableTestUser()->getUser();
                $userIdentity = $this->getMock( UserIdentity::class );
                $userIdentity->method( 'getId' )->willReturn( $user->getId() );
                $userIdentity->method( 'getName' )->willReturn( $user->getName() );
                $userIdentity->method( 'getActorId' )->willReturn( 0 );
 
-               list( $cFields, $cCallback ) = MediaWikiServices::getInstance()->getCommentStore()
-                       ->insertWithTempTable( $this->db, 'rev_comment', '' );
                $m = new ActorMigration( $stage );
                list( $fields, $callback ) =
-                       $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
-               $extraFields = [
-                       'rev_page' => 42,
-                       'rev_len' => 0,
-                       'rev_timestamp' => $this->db->timestamp(),
-               ] + $cFields;
-               $this->db->insert( 'revision', $extraFields + $fields, __METHOD__ );
-               $id = $this->db->insertId();
-               $callback( $id, $extraFields );
-               $cCallback( $id );
-
-               $qi = $m->getJoin( 'rev_user' );
+                       $m->getInsertValuesWithTempTable( $this->db, 'am2_user', $userIdentity );
+               $id = ++self::$amId;
+               $this->db->insert( 'actormigration2', [ 'am2_id' => $id ] + $fields, __METHOD__ );
+               $callback( $id, [] );
+
+               $qi = $m->getJoin( 'am2_user' );
                $row = $this->db->selectRow(
-                       [ 'revision' ] + $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
+                       [ 'actormigration2' ] + $qi['tables'],
+                       $qi['fields'],
+                       [ 'am2_id' => $id ],
+                       __METHOD__,
+                       [],
+                       $qi['joins']
                );
-               $this->assertSame( $user->getId(), (int)$row->rev_user );
-               $this->assertSame( $user->getName(), $row->rev_user_text );
+               $this->assertSame( $user->getId(), (int)$row->am2_user );
+               $this->assertSame( $user->getName(), $row->am2_user_text );
                $this->assertSame(
                        ( $stage & SCHEMA_COMPAT_READ_NEW ) ? $user->getActorId() : 0,
-                       (int)$row->rev_actor
+                       (int)$row->am2_actor
                );
 
                $m = new ActorMigration( $stage );
@@ -736,4 +748,24 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                ];
        }
 
+       public function testCheckDeprecation() {
+               $wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
+               $wrap->deprecated += [ 'soft' => null, 'hard' => '1.34' ];
+               $wrap->removed += [ 'gone' => '1.34' ];
+
+               $this->hideDeprecated( 'ActorMigration for \'hard\'' );
+
+               $wrap->checkDeprecation( 'valid' );
+               $wrap->checkDeprecation( 'soft' );
+               $wrap->checkDeprecation( 'hard' );
+               try {
+                       $wrap->checkDeprecation( 'gone' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Use of ActorMigration for \'gone\' was removed in MediaWiki 1.34',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
 }
diff --git a/tests/phpunit/includes/ActorMigrationTest.sql b/tests/phpunit/includes/ActorMigrationTest.sql
new file mode 100644 (file)
index 0000000..f387dac
--- /dev/null
@@ -0,0 +1,26 @@
+-- These are carefully crafted to work in all five supported databases
+
+CREATE TABLE /*_*/actormigration1 (
+  am1_id integer not null,
+  am1_user integer,
+  am1_user_text varchar(200),
+  am1_actor integer
+);
+
+CREATE TABLE /*_*/actormigration2 (
+  am2_id integer not null,
+  am2_user integer,
+  am2_user_text varchar(200)
+);
+
+CREATE TABLE /*_*/actormigration2_temp (
+  am2t_id integer not null,
+  am2t_actor integer
+);
+
+CREATE TABLE /*_*/actormigration3 (
+  am3_id integer not null,
+  am3_xxx integer,
+  am3_xxx_text varchar(200),
+  am3_xxx_actor integer
+);
index 233810f..53539ea 100644 (file)
@@ -7,13 +7,13 @@ use MediaWikiTestCase;
 use Message;
 use Wikimedia\Message\MessageValue;
 use Wikimedia\Message\ParamType;
-use Wikimedia\Message\TextParam;
+use Wikimedia\Message\ScalarParam;
 
 /**
  * @covers \MediaWiki\Message\TextFormatter
  * @covers \Wikimedia\Message\MessageValue
  * @covers \Wikimedia\Message\ListParam
- * @covers \Wikimedia\Message\TextParam
+ * @covers \Wikimedia\Message\ScalarParam
  * @covers \Wikimedia\Message\MessageParam
  */
 class TextFormatterTest extends MediaWikiTestCase {
@@ -45,11 +45,24 @@ class TextFormatterTest extends MediaWikiTestCase {
                $formatter = $this->createTextFormatter( 'en' );
                $mv = ( new MessageValue( 'test' ) )->commaListParams( [
                        'a',
-                       new TextParam( ParamType::BITRATE, 100 ),
+                       new ScalarParam( ParamType::BITRATE, 100 ),
                ] );
                $result = $formatter->format( $mv );
                $this->assertSame( 'test a, 100 bps $2', $result );
        }
+
+       public function testFormatMessage() {
+               $formatter = $this->createTextFormatter( 'en' );
+               $mv = ( new MessageValue( 'test' ) )
+                       ->params( new MessageValue( 'test2', [ 'a', 'b' ] ) )
+                       ->commaListParams( [
+                               'x',
+                               new ScalarParam( ParamType::BITRATE, 100 ),
+                               new MessageValue( 'test3', [ 'c', new MessageValue( 'test4', [ 'd', 'e' ] ) ] )
+                       ] );
+               $result = $formatter->format( $mv );
+               $this->assertSame( 'test test2 a b x, 100 bps, test3 c test4 d e', $result );
+       }
 }
 
 class FakeMessage extends Message {
index 3c6573a..2d1fd98 100644 (file)
@@ -9,8 +9,11 @@ use MediaWiki\Rest\Handler;
 use MediaWiki\Rest\RequestData;
 use MediaWiki\Rest\ResponseFactory;
 use MediaWiki\Rest\Router;
+use MediaWiki\Rest\Validator\Validator;
 use MediaWikiTestCase;
+use Psr\Container\ContainerInterface;
 use User;
+use Wikimedia\ObjectFactory;
 
 /**
  * @group Database
@@ -21,7 +24,7 @@ use User;
  * @covers \MediaWiki\Rest\BasicAccess\MWBasicRequestAuthorizer
  */
 class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
-       private function createRouter( $userRights ) {
+       private function createRouter( $userRights, $request ) {
                $user = User::newFromName( 'Test user' );
                // Don't allow the rights to everybody so that user rights kick in.
                $this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [ '*' => $userRights ] );
@@ -34,18 +37,25 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
 
                global $IP;
 
+               $objectFactory = new ObjectFactory(
+                       $this->getMockForAbstractClass( ContainerInterface::class )
+               );
+
                return new Router(
                        [ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
                        [],
                        '/rest',
                        new \EmptyBagOStuff(),
                        new ResponseFactory(),
-                       new MWBasicAuthorizer( $user, MediaWikiServices::getInstance()->getPermissionManager() ) );
+                       new MWBasicAuthorizer( $user, MediaWikiServices::getInstance()->getPermissionManager() ),
+                       $objectFactory,
+                       new Validator( $objectFactory, $request, $user )
+               );
        }
 
        public function testReadDenied() {
-               $router = $this->createRouter( [ 'read' => false ] );
                $request = new RequestData( [ 'uri' => new Uri( '/rest/user/joe/hello' ) ] );
+               $router = $this->createRouter( [ 'read' => false ], $request );
                $response = $router->execute( $request );
                $this->assertSame( 403, $response->getStatusCode() );
 
@@ -56,8 +66,8 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
        }
 
        public function testReadAllowed() {
-               $router = $this->createRouter( [ 'read' => true ] );
                $request = new RequestData( [ 'uri' => new Uri( '/rest/user/joe/hello' ) ] );
+               $router = $this->createRouter( [ 'read' => true ], $request );
                $response = $router->execute( $request );
                $this->assertSame( 200, $response->getStatusCode() );
        }
@@ -75,10 +85,10 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
        }
 
        public function testWriteDenied() {
-               $router = $this->createRouter( [ 'read' => true, 'writeapi' => false ] );
                $request = new RequestData( [
                        'uri' => new Uri( '/rest/mock/MWBasicRequestAuthorizerTest/write' )
                ] );
+               $router = $this->createRouter( [ 'read' => true, 'writeapi' => false ], $request );
                $response = $router->execute( $request );
                $this->assertSame( 403, $response->getStatusCode() );
 
@@ -89,10 +99,10 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
        }
 
        public function testWriteAllowed() {
-               $router = $this->createRouter( [ 'read' => true, 'writeapi' => true ] );
                $request = new RequestData( [
                        'uri' => new Uri( '/rest/mock/MWBasicRequestAuthorizerTest/write' )
                ] );
+               $router = $this->createRouter( [ 'read' => true, 'writeapi' => true ], $request );
                $response = $router->execute( $request );
 
                $this->assertSame( 200, $response->getStatusCode() );
index b599e9d..b984895 100644 (file)
@@ -9,10 +9,15 @@ use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
 use MediaWiki\Rest\Handler;
 use MediaWiki\Rest\EntryPoint;
 use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\RequestInterface;
 use MediaWiki\Rest\ResponseFactory;
 use MediaWiki\Rest\Router;
+use MediaWiki\Rest\Validator\Validator;
+use Psr\Container\ContainerInterface;
 use RequestContext;
 use WebResponse;
+use Wikimedia\ObjectFactory;
+use User;
 
 /**
  * @covers \MediaWiki\Rest\EntryPoint
@@ -21,16 +26,23 @@ use WebResponse;
 class EntryPointTest extends \MediaWikiTestCase {
        private static $mockHandler;
 
-       private function createRouter() {
+       private function createRouter( RequestInterface $request ) {
                global $IP;
 
+               $objectFactory = new ObjectFactory(
+                       $this->getMockForAbstractClass( ContainerInterface::class )
+               );
+
                return new Router(
                        [ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
                        [],
                        '/rest',
                        new EmptyBagOStuff(),
                        new ResponseFactory(),
-                       new StaticBasicAuthorizer() );
+                       new StaticBasicAuthorizer(),
+                       $objectFactory,
+                       new Validator( $objectFactory, $request, new User )
+               );
        }
 
        private function createWebResponse() {
@@ -58,11 +70,12 @@ class EntryPointTest extends \MediaWikiTestCase {
                                [ 'Foo: Bar', true, null ]
                        );
 
+               $request = new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] );
                $entryPoint = new EntryPoint(
                        RequestContext::getMain(),
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+                       $request,
                        $webResponse,
-                       $this->createRouter() );
+                       $this->createRouter( $request ) );
                $entryPoint->execute();
                $this->assertTrue( true );
        }
@@ -83,11 +96,12 @@ class EntryPointTest extends \MediaWikiTestCase {
         * Make sure EntryPoint rewinds a seekable body stream before reading.
         */
        public function testBodyRewind() {
+               $request = new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] );
                $entryPoint = new EntryPoint(
                        RequestContext::getMain(),
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+                       $request,
                        $this->createWebResponse(),
-                       $this->createRouter() );
+                       $this->createRouter( $request ) );
                ob_start();
                $entryPoint->execute();
                $this->assertSame( 'hello', ob_get_clean() );
index 5cc3646..949b664 100644 (file)
@@ -61,32 +61,11 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                ];
        }
 
-       protected function getOldActorQueryFields( $prefix ) {
-               return [
-                       "{$prefix}_user" => "{$prefix}_user",
-                       "{$prefix}_user_text" => "{$prefix}_user_text",
-                       "{$prefix}_actor" => 'NULL',
-               ];
-       }
-
        protected function getNewActorQueryFields( $prefix, $tmp = false ) {
                return [
                        "{$prefix}_user" => "actor_{$prefix}_user.actor_user",
                        "{$prefix}_user_text" => "actor_{$prefix}_user.actor_name",
-                       "{$prefix}_actor" => $tmp ?: "{$prefix}_actor",
-               ];
-       }
-
-       protected function getNewActorJoins( $prefix ) {
-               return [
-                       "temp_{$prefix}_user" => [
-                               "JOIN",
-                               "temp_{$prefix}_user.revactor_{$prefix} = {$prefix}_id",
-                       ],
-                       "actor_{$prefix}_user" => [
-                               "JOIN",
-                               "actor_{$prefix}_user.actor_id = temp_{$prefix}_user.revactor_actor",
-                       ],
+                       "{$prefix}_actor" => $tmp ? "temp_{$prefix}_user.{$prefix}actor_actor" : "{$prefix}_actor",
                ];
        }
 
@@ -125,7 +104,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                yield 'MCR, comment, actor' => [
                        [
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        ],
                        [
                                'tables' => [
@@ -150,7 +128,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        ],
                        [
                                'tables' => [
@@ -175,22 +152,23 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        [
                                'tables' => [
                                        'archive',
+                                       'actor_ar_user' => 'actor',
                                        'comment_ar_comment' => 'comment',
                                ],
                                'fields' => array_merge(
                                        $this->getArchiveQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'ar' ),
-                                       $this->getOldActorQueryFields( 'ar' ),
+                                       $this->getNewActorQueryFields( 'ar' ),
                                        $this->getNewCommentQueryFields( 'ar' )
                                ),
                                'joins' => [
                                        'comment_ar_comment'
                                                => [ 'JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
+                                       'actor_ar_user' => [ 'JOIN', 'actor_ar_user.actor_id = ar_actor' ],
                                ],
                        ]
                ];
@@ -198,21 +176,22 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [
                                'tables' => [
                                        'archive',
+                                       'actor_ar_user' => 'actor',
                                        'comment_ar_comment' => 'comment',
                                ],
                                'fields' => array_merge(
                                        $this->getArchiveQueryFields( true ),
-                                       $this->getOldActorQueryFields( 'ar' ),
+                                       $this->getNewActorQueryFields( 'ar' ),
                                        $this->getNewCommentQueryFields( 'ar' )
                                ),
                                'joins' => [
                                        'comment_ar_comment'
                                                => [ 'JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
+                                       'actor_ar_user' => [ 'JOIN', 'actor_ar_user.actor_id = ar_actor' ],
                                ],
                        ]
                ];
@@ -224,7 +203,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -260,6 +238,8 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        ],
                                        'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ]
                ];
@@ -268,7 +248,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -288,22 +267,21 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
-                               'joins' => array_merge(
-                                       [
-                                               'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
-                                               'user' => [
-                                                       'LEFT JOIN',
-                                                       [
-                                                               'actor_rev_user.actor_user != 0',
-                                                               'user_id = actor_rev_user.actor_user',
-                                                       ]
-                                               ],
-                                               'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
-                                               'comment_rev_comment'
-                                                       => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                               'joins' => [
+                                       'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
+                                       'user' => [
+                                               'LEFT JOIN',
+                                               [
+                                                       'actor_rev_user.actor_user != 0',
+                                                       'user_id = actor_rev_user.actor_user',
+                                               ]
                                        ],
-                                       $this->getNewActorJoins( 'rev' )
-                               ),
+                                       'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                       'comment_rev_comment'
+                                               => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                               ],
                        ]
                ];
                yield 'MCR read-new' => [
@@ -311,7 +289,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -331,22 +308,21 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
-                               'joins' => array_merge(
-                                       [
-                                               'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
-                                               'user' => [
-                                                       'LEFT JOIN',
-                                                       [
-                                                               'actor_rev_user.actor_user != 0',
-                                                               'user_id = actor_rev_user.actor_user'
-                                                       ]
-                                               ],
-                                               'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
-                                               'comment_rev_comment'
-                                                       => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                               'joins' => [
+                                       'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
+                                       'user' => [
+                                               'LEFT JOIN',
+                                               [
+                                                       'actor_rev_user.actor_user != 0',
+                                                       'user_id = actor_rev_user.actor_user'
+                                               ]
                                        ],
-                                       $this->getNewActorJoins( 'rev' )
-                               ),
+                                       'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                       'comment_rev_comment'
+                                               => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                               ],
                        ]
                ];
                yield 'MCR write-both/read-old' => [
@@ -354,7 +330,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        [],
                        [
@@ -362,17 +337,21 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'rev' ),
-                                       $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+                                       $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ]
                ];
@@ -381,7 +360,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage'
                                        => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -391,37 +369,38 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'user',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'rev' ),
                                        $this->getUserQueryFields(),
                                        $this->getPageQueryFields(),
-                                       $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+                                       $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
-                               'joins' => array_merge(
-                                       [
-                                               'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
-                                               'user' => [
-                                                       'LEFT JOIN',
-                                                       [
-                                                               'rev_user != 0',
-                                                               'user_id = rev_user'
-                                                       ]
-                                               ],
-                                               'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
-                                               'comment_rev_comment'
-                                                       => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
-                                       ]
-                               ),
+                               'joins' => [
+                                       'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
+                                       'user' => [
+                                               'LEFT JOIN',
+                                               [
+                                                       'actor_rev_user.actor_user != 0',
+                                                       'user_id = actor_rev_user.actor_user',
+                                               ]
+                                       ],
+                                       'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                       'comment_rev_comment'
+                                               => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                               ],
                        ]
                ];
                yield 'pre-MCR' => [
                        [
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [],
                        [
@@ -429,17 +408,21 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'rev' ),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ]
                ];
@@ -447,7 +430,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => true,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'page', 'user' ],
                        [
@@ -455,21 +437,28 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision', 'page', 'user',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getContentHandlerQueryFields( 'rev' ),
                                        $this->getPageQueryFields(),
                                        $this->getUserQueryFields(),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
                                        'page' => [ 'JOIN', [ 'page_id = rev_page' ] ],
-                                       'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+                                       'user' => [ 'LEFT JOIN', [
+                                               'actor_rev_user.actor_user != 0',
+                                               'user_id = actor_rev_user.actor_user',
+                                       ] ],
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ]
                ];
@@ -477,7 +466,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [],
                        [
@@ -485,16 +473,20 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ],
                ];
@@ -502,7 +494,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'page' ],
                        [
@@ -510,11 +501,13 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision', 'page',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getPageQueryFields(),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
@@ -522,6 +515,8 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ],
                ];
@@ -529,7 +524,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'user' ],
                        [
@@ -537,18 +531,25 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision', 'user',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getUserQueryFields(),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
-                                       'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+                                       'user' => [ 'LEFT JOIN', [
+                                               'actor_rev_user.actor_user != 0',
+                                               'user_id = actor_rev_user.actor_user',
+                                       ] ],
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ],
                ];
@@ -556,7 +557,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'text' ],
                        [
@@ -564,11 +564,13 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision', 'text',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getTextQueryFields(),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
@@ -576,6 +578,8 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ],
                ];
@@ -583,7 +587,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [
                                'wgContentHandlerUseDB' => false,
                                'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
                        ],
                        [ 'text', 'page', 'user' ],
                        [
@@ -591,13 +594,15 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'revision', 'page', 'user', 'text',
                                        'temp_rev_comment' => 'revision_comment_temp',
                                        'comment_rev_comment' => 'comment',
+                                       'temp_rev_user' => 'revision_actor_temp',
+                                       'actor_rev_user' => 'actor',
                                ],
                                'fields' => array_merge(
                                        $this->getRevisionQueryFields( true ),
                                        $this->getPageQueryFields(),
                                        $this->getUserQueryFields(),
                                        $this->getTextQueryFields(),
-                                       $this->getOldActorQueryFields( 'rev' ),
+                                       $this->getNewActorQueryFields( 'rev', true ),
                                        $this->getNewCommentQueryFields( 'rev' )
                                ),
                                'joins' => [
@@ -608,8 +613,8 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'user' => [
                                                'LEFT JOIN',
                                                [
-                                                       'rev_user != 0',
-                                                       'user_id = rev_user',
+                                                       'actor_rev_user.actor_user != 0',
+                                                       'user_id = actor_rev_user.actor_user',
                                                ],
                                        ],
                                        'text' => [
@@ -619,6 +624,8 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                                        'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
                                        'comment_rev_comment'
                                                => [ 'JOIN', 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
                                ],
                        ],
                ];
@@ -848,190 +855,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                ];
        }
 
-       public function provideSelectFields() {
-               yield 'with model, comment, and actor' => [
-                       [
-                               'wgContentHandlerUseDB' => true,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-                       ],
-                       'fields' => array_merge(
-                               [
-                                       'rev_id',
-                                       'rev_page',
-                                       'rev_text_id',
-                                       'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
-                                       'rev_actor' => 'NULL',
-                                       'rev_minor_edit',
-                                       'rev_deleted',
-                                       'rev_len',
-                                       'rev_parent_id',
-                                       'rev_sha1',
-                               ],
-                               $this->getContentHandlerQueryFields( 'rev' ),
-                               [
-                                       'rev_comment_pk' => 'rev_id',
-                               ]
-                       ),
-               ];
-               yield 'no mode, no comment, no actor' => [
-                       [
-                               'wgContentHandlerUseDB' => false,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                       ],
-                       'fields' => array_merge(
-                               [
-                                       'rev_id',
-                                       'rev_page',
-                                       'rev_text_id',
-                                       'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
-                                       'rev_actor' => 'NULL',
-                                       'rev_minor_edit',
-                                       'rev_deleted',
-                                       'rev_len',
-                                       'rev_parent_id',
-                                       'rev_sha1',
-                                       'rev_comment_pk' => 'rev_id',
-                               ]
-                       ),
-               ];
-       }
-
-       public function provideSelectArchiveFields() {
-               yield 'with model, comment, and actor' => [
-                       [
-                               'wgContentHandlerUseDB' => true,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-                       ],
-                       'fields' => array_merge(
-                               [
-                                       'ar_id',
-                                       'ar_page_id',
-                                       'ar_rev_id',
-                                       'ar_text_id',
-                                       'ar_timestamp',
-                                       'ar_user_text',
-                                       'ar_user',
-                                       'ar_actor' => 'NULL',
-                                       'ar_minor_edit',
-                                       'ar_deleted',
-                                       'ar_len',
-                                       'ar_parent_id',
-                                       'ar_sha1',
-                               ],
-                               $this->getContentHandlerQueryFields( 'ar' ),
-                               [
-                                       'ar_comment_id' => 'ar_comment_id',
-                               ]
-                       ),
-               ];
-               yield 'no mode, no comment, no actor' => [
-                       [
-                               'wgContentHandlerUseDB' => false,
-                               'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
-                       ],
-                       'fields' => array_merge(
-                               [
-                                       'ar_id',
-                                       'ar_page_id',
-                                       'ar_rev_id',
-                                       'ar_text_id',
-                                       'ar_timestamp',
-                                       'ar_user_text',
-                                       'ar_user',
-                                       'ar_actor' => 'NULL',
-                                       'ar_minor_edit',
-                                       'ar_deleted',
-                                       'ar_len',
-                                       'ar_parent_id',
-                                       'ar_sha1',
-                                       'ar_comment_id' => 'ar_comment_id',
-                               ]
-                       ),
-               ];
-       }
-
-       /**
-        * @dataProvider provideSelectFields
-        * @covers Revision::selectFields
-        */
-       public function testRevisionSelectFields( $migrationStageSettings, $expected ) {
-               $this->setMwGlobals( $migrationStageSettings );
-
-               $this->hideDeprecated( 'Revision::selectFields' );
-               $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectFields() );
-       }
-
-       /**
-        * @dataProvider provideSelectArchiveFields
-        * @covers Revision::selectArchiveFields
-        */
-       public function testRevisionSelectArchiveFields( $migrationStageSettings, $expected ) {
-               $this->setMwGlobals( $migrationStageSettings );
-
-               $this->hideDeprecated( 'Revision::selectArchiveFields' );
-               $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectArchiveFields() );
-       }
-
-       /**
-        * @covers Revision::userJoinCond
-        */
-       public function testRevisionUserJoinCond() {
-               $this->hideDeprecated( 'Revision::userJoinCond' );
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
-               $this->assertEquals(
-                       [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
-                       Revision::userJoinCond()
-               );
-       }
-
-       /**
-        * @covers Revision::pageJoinCond
-        */
-       public function testRevisionPageJoinCond() {
-               $this->hideDeprecated( 'Revision::pageJoinCond' );
-               $this->assertEquals(
-                       [ 'JOIN', [ 'page_id = rev_page' ] ],
-                       Revision::pageJoinCond()
-               );
-       }
-
-       /**
-        * @covers Revision::selectTextFields
-        */
-       public function testRevisionSelectTextFields() {
-               $this->hideDeprecated( 'Revision::selectTextFields' );
-               $this->assertEquals(
-                       $this->getTextQueryFields(),
-                       Revision::selectTextFields()
-               );
-       }
-
-       /**
-        * @covers Revision::selectPageFields
-        */
-       public function testRevisionSelectPageFields() {
-               $this->hideDeprecated( 'Revision::selectPageFields' );
-               $this->assertEquals(
-                       $this->getPageQueryFields(),
-                       Revision::selectPageFields()
-               );
-       }
-
-       /**
-        * @covers Revision::selectUserFields
-        */
-       public function testRevisionSelectUserFields() {
-               $this->hideDeprecated( 'Revision::selectUserFields' );
-               $this->assertEquals(
-                       $this->getUserQueryFields(),
-                       Revision::selectUserFields()
-               );
-       }
-
        /**
         * @covers Revision::getArchiveQueryInfo
         * @dataProvider provideArchiveQueryInfo
index b0b9ddf..76190e0 100644 (file)
@@ -81,7 +81,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
                        'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
-                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                ] );
        }
 
@@ -1791,8 +1790,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         * @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
         */
        public function testGetKnownCurrentRevision_userNameChange() {
-               global $wgActorTableSchemaMigrationStage;
-
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
                $this->setService( 'MainWANObjectCache', $cache );
 
@@ -1811,11 +1808,9 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                $this->db->update( 'user',
                        [ 'user_name' => $newUserName ],
                        [ 'user_id' => $rev->getUser()->getId() ] );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $this->db->update( 'actor',
-                               [ 'actor_name' => $newUserName ],
-                               [ 'actor_user' => $rev->getUser()->getId() ] );
-               }
+               $this->db->update( 'actor',
+                       [ 'actor_name' => $newUserName ],
+                       [ 'actor_user' => $rev->getUser()->getId() ] );
 
                // Reload the revision and regrab the user name.
                $revAfter = $store->getKnownCurrentRevision( $page->getTitle(), $rev->getId() );
@@ -1864,8 +1859,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
         */
        public function testNewRevisionFromRow_userNameChange() {
-               global $wgActorTableSchemaMigrationStage;
-
                $page = $this->getTestPage();
                $text = __METHOD__;
                /** @var Revision $rev */
@@ -1895,11 +1888,9 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                $this->db->update( 'user',
                        [ 'user_name' => $newUserName ],
                        [ 'user_id' => $record->getUser()->getId() ] );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                       $this->db->update( 'actor',
-                               [ 'actor_name' => $newUserName ],
-                               [ 'actor_user' => $record->getUser()->getId() ] );
-               }
+               $this->db->update( 'actor',
+                       [ 'actor_name' => $newUserName ],
+                       [ 'actor_user' => $record->getUser()->getId() ] );
 
                // Reload the record, passing $fromCache as true to force fresh info from the db,
                // and regrab the user name
index 7b334ee..d41c88f 100644 (file)
@@ -92,7 +92,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
                        'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
-                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                ] );
 
                if ( !$this->testPage ) {
index 865bd31..ed4a27f 100644 (file)
@@ -601,7 +601,6 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::loadFromTitle
         */
        public function testLoadFromTitle() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
                $title = $this->getMockTitle();
 
                $conditions = [
index 18faeea..82ccb9a 100644 (file)
@@ -768,12 +768,12 @@ class TitleTest extends MediaWikiTestCase {
                $this->assertEquals(
                        false,
                        $title->exists(),
-                       'exists() should rely on link cache unless GAID_FOR_UPDATE is used'
+                       'exists() should rely on link cache unless READ_LATEST is used'
                );
                $this->assertEquals(
                        true,
-                       $title->exists( Title::GAID_FOR_UPDATE ),
-                       'exists() should re-query database when GAID_FOR_UPDATE is used'
+                       $title->exists( Title::READ_LATEST ),
+                       'exists() should re-query database when READ_LATEST is used'
                );
        }
 
index c68954c..d2bbc1f 100644 (file)
@@ -67,7 +67,7 @@ class ApiDeleteTest extends ApiTestCase {
                $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
                $jobs->execute();
 
-               $this->assertFalse( Title::newFromText( $name )->exists( Title::GAID_FOR_UPDATE ) );
+               $this->assertFalse( Title::newFromText( $name )->exists( Title::READ_LATEST ) );
        }
 
        public function testDeleteNonexistent() {
index 1e2135b..3a3f5f1 100644 (file)
@@ -242,11 +242,12 @@ class ApiMainTest extends ApiTestCase {
                $mock->method( 'needsToken' )->willReturn( true );
 
                $api = new ApiMain( new FauxRequest( [ 'action' => 'testmodule' ] ) );
-               $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
-                       function () use ( $mock ) {
+               $api->getModuleManager()->addModule( 'testmodule', 'action', [
+                       'class' => get_class( $mock ),
+                       'factory' => function () use ( $mock ) {
                                return $mock;
                        }
-               );
+               );
                $api->execute();
        }
 
@@ -260,11 +261,12 @@ class ApiMainTest extends ApiTestCase {
                $mock->method( 'mustBePosted' )->willReturn( false );
 
                $api = new ApiMain( new FauxRequest( [ 'action' => 'testmodule' ] ) );
-               $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
-                       function () use ( $mock ) {
+               $api->getModuleManager()->addModule( 'testmodule', 'action', [
+                       'class' => get_class( $mock ),
+                       'factory' => function () use ( $mock ) {
                                return $mock;
                        }
-               );
+               );
                $api->execute();
        }
 
@@ -309,11 +311,12 @@ class ApiMainTest extends ApiTestCase {
                $req->setRequestURL( "http://localhost" );
 
                $api = new ApiMain( $req );
-               $api->getModuleManager()->addModule( 'testmodule', 'action', get_class( $mock ),
-                       function () use ( $mock ) {
+               $api->getModuleManager()->addModule( 'testmodule', 'action', [
+                       'class' => get_class( $mock ),
+                       'factory' => function () use ( $mock ) {
                                return $mock;
                        }
-               );
+               );
 
                $wrapper = TestingAccessWrapper::newFromObject( $api );
                $wrapper->mInternalMode = false;
index b01b90e..e99e9a9 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+use Wikimedia\ObjectFactory;
+
 /**
  * @covers ApiModuleManager
  *
@@ -12,7 +15,8 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
        private function getModuleManager() {
                $request = new FauxRequest();
                $main = new ApiMain( $request );
-               return new ApiModuleManager( $main );
+
+               return new ApiModuleManager( $main, MediaWikiServices::getInstance()->getObjectFactory() );
        }
 
        public function newApiLogin( $main, $action ) {
@@ -28,30 +32,61 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
                                null,
                        ],
 
-                       'with factory' => [
+                       'with class and factory' => [
                                'login',
                                'action',
                                ApiLogin::class,
                                [ $this, 'newApiLogin' ],
                        ],
 
-                       'with closure' => [
-                               'logout',
+                       'with spec (class only)' => [
+                               'login',
                                'action',
-                               ApiLogout::class,
-                               function ( ApiMain $main, $action ) {
-                                       return new ApiLogout( $main, $action );
-                               },
+                               [
+                                       'class' => ApiLogin::class
+                               ],
+                               null,
+                       ],
+
+                       'with spec' => [
+                               'login',
+                               'action',
+                               [
+                                       'class' => ApiLogin::class,
+                                       'factory' => [ $this, 'newApiLogin' ],
+                               ],
+                               null,
                        ],
+
+                       'with spec (using services)' => [
+                               'logout',
+                               'action',
+                               [
+                                       'class' => ApiLogout::class,
+                                       'factory' => function ( ApiMain $main, $action, ObjectFactory $objectFactory ) {
+                                               return new ApiLogout( $main, $action );
+                                       },
+                                       'services' => [
+                                               'ObjectFactory'
+                                       ],
+                               ],
+                               null,
+                       ]
                ];
        }
 
        /**
         * @dataProvider addModuleProvider
         */
-       public function testAddModule( $name, $group, $class, $factory = null ) {
+       public function testAddModule( $name, $group, $spec, $factory ) {
+               if ( $factory ) {
+                       $this->hideDeprecated(
+                               ApiModuleManager::class . '::addModule with $class and $factory'
+                       );
+               }
+
                $moduleManager = $this->getModuleManager();
-               $moduleManager->addModule( $name, $group, $class, $factory );
+               $moduleManager->addModule( $name, $group, $spec, $factory );
 
                $this->assertTrue( $moduleManager->isDefined( $name, $group ), 'isDefined' );
                $this->assertNotNull( $moduleManager->getModule( $name, $group, true ), 'getModule' );
@@ -327,4 +362,22 @@ class ApiModuleManagerTest extends MediaWikiTestCase {
                        $moduleManager->getClassName( 'nonexistentmodule' )
                );
        }
+
+       /**
+        * @expectedException \InvalidArgumentException
+        * @expectedExceptionMessage $spec must define a class name
+        */
+       public function testAddModuleWithIncompleteSpec() {
+               $moduleManager = $this->getModuleManager();
+
+               $moduleManager->addModule(
+                       'logout',
+                       'action',
+                       [
+                               'factory' => function ( ApiMain $main, $action ) {
+                                       return new ApiLogout( $main, $action );
+                               },
+                       ]
+               );
+       }
 }
index b9e4645..fdc9c1b 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
+
 /**
  * @group API
  * @group medium
@@ -176,4 +179,44 @@ class ApiPageSetTest extends ApiTestCase {
                        3 => [ "$userDbkey/subpage" => -3 ],
                ], $pageSet->getAllTitlesByNamespace() );
        }
+
+       /**
+        * Test that ApiPageSet is calling GenderCache for provided user names to prefill the
+        * GenderCache and avoid a performance issue when loading each users' gender on it's own.
+        * The test is setting the "missLimit" to 0 on the GenderCache to trigger misses logic.
+        * When the "misses" property is no longer 0 at the end of the test,
+        * something was requested which is not part of the cache. Than the test is failing.
+        */
+       public function testGenderCaching() {
+               // Set up the user namespace to have gender aliases to trigger the gender cache
+               $this->setMwGlobals( [
+                       'wgExtraGenderNamespaces' => [ NS_USER => [ 'male' => 'Male', 'female' => 'Female' ] ]
+               ] );
+               $this->overrideMwServices();
+
+               // User names to test with - it is not needed that the user exists in the database
+               // to trigger gender cache
+               $userNames = [
+                       'Female',
+                       'Unknown',
+                       'Male',
+               ];
+
+               // Prepare the gender cache for testing - this is a fresh instance due to service override
+               $genderCache = TestingAccessWrapper::newFromObject(
+                       MediaWikiServices::getInstance()->getGenderCache()
+               );
+               $genderCache->missLimit = 0;
+
+               // Do an api request to trigger ApiPageSet code
+               $this->doApiRequest( [
+                       'action' => 'query',
+                       'titles' => 'User:' . implode( '|User:', $userNames ),
+               ] );
+
+               $this->assertEquals( 0, $genderCache->misses,
+                       'ApiPageSet does not prefill the gender cache correctly' );
+               $this->assertEquals( $userNames, array_keys( $genderCache->cache ),
+                       'ApiPageSet does not prefill all users into the gender cache' );
+       }
 }
index 6bbdd3b..771e039 100644 (file)
@@ -44,14 +44,16 @@ class ApiQueryLanguageinfoTest extends ApiTestCase {
                                        $moduleManager->addModule(
                                                'languageinfo',
                                                'meta',
-                                               ApiQueryLanguageinfo::class,
-                                               function ( $parent, $name ) use ( $microtimeFunction ) {
-                                                       return new ApiQueryLanguageinfo(
-                                                               $parent,
-                                                               $name,
-                                                               $microtimeFunction
-                                                       );
-                                               }
+                                               [
+                                                       'class' => ApiQueryLanguageinfo::class,
+                                                       'factory' => function ( $parent, $name ) use ( $microtimeFunction ) {
+                                                               return new ApiQueryLanguageinfo(
+                                                                       $parent,
+                                                                       $name,
+                                                                       $microtimeFunction
+                                                               );
+                                                       }
+                                               ]
                                        );
                                }
                        );
index a4ca8a1..5dcea65 100644 (file)
@@ -22,8 +22,7 @@ class ApiRevisionDeleteTest extends ApiTestCase {
                // Make a few edits for us to play with
                for ( $i = 1; $i <= 5; $i++ ) {
                        self::editPage( self::$page, MWCryptRand::generateHex( 10 ), 'summary' );
-                       $this->revs[] = Title::newFromText( self::$page )
-                               ->getLatestRevID( Title::GAID_FOR_UPDATE );
+                       $this->revs[] = Title::newFromText( self::$page )->getLatestRevID( Title::READ_LATEST );
                }
        }
 
index 03359f8..286e8a8 100644 (file)
@@ -365,13 +365,19 @@ class ApiFormatBaseTest extends ApiFormatTestBase {
                $main = new ApiMain( $context );
                $printer = $this->getMockFormatter( $main, 'mockfm' );
                $mm = $printer->getMain()->getModuleManager();
-               $mm->addModule( 'mockfm', 'format', ApiFormatBase::class, function () {
-                       return $mock;
-               } );
-               if ( $registerNonHtml ) {
-                       $mm->addModule( 'mock', 'format', ApiFormatBase::class, function () {
+               $mm->addModule( 'mockfm', 'format', [
+                       'class' => ApiFormatBase::class,
+                       'factory' => function () {
                                return $mock;
-                       } );
+                       }
+               ] );
+               if ( $registerNonHtml ) {
+                       $mm->addModule( 'mock', 'format', [
+                               'class' => ApiFormatBase::class,
+                               'factory' => function () {
+                                       return $mock;
+                               }
+                       ] );
                }
 
                $printer->initPrinter();
index 27ec73a..789908c 100644 (file)
@@ -27,7 +27,6 @@ abstract class ApiFormatTestBase extends MediaWikiTestCase {
         *  - class: If set, register 'name' with this class (and 'factory', if that's set)
         *  - factory: Used with 'class' to register at runtime
         *  - returnPrinter: Return the printer object
-        * @param callable|null $factory Factory to use instead of the normal one
         * @return string|array The string if $options['returnPrinter'] isn't set, or an array if it is:
         *  - text: Output text string
         *  - printer: ApiFormatBase
@@ -44,8 +43,15 @@ abstract class ApiFormatTestBase extends MediaWikiTestCase {
                $context->setRequest( new FauxRequest( $params, true ) );
                $main = new ApiMain( $context );
                if ( isset( $options['class'] ) ) {
-                       $factory = $options['factory'] ?? null;
-                       $main->getModuleManager()->addModule( $printerName, 'format', $options['class'], $factory );
+                       $spec = [
+                               'class' => $options['class']
+                       ];
+
+                       if ( isset( $options['factory'] ) ) {
+                               $spec['factory'] = $options['factory'];
+                       }
+
+                       $main->getModuleManager()->addModule( $printerName, 'format', $spec );
                }
                $result = $main->getResult();
                $result->addArrayType( null, 'default' );
index 301ed10..df643d2 100644 (file)
@@ -8,15 +8,6 @@
  */
 class ApiQueryUserContribsTest extends ApiTestCase {
        public function addDBDataOnce() {
-               global $wgActorTableSchemaMigrationStage;
-
-               $reset = new \Wikimedia\ScopedCallback( function ( $v ) {
-                       $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $v );
-               }, [ $wgActorTableSchemaMigrationStage ] );
-               // Needs to WRITE_BOTH so READ_OLD tests below work. READ mode here doesn't really matter.
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage',
-                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
-
                $users = [
                        User::newFromName( '192.168.2.2', false ),
                        User::newFromName( '192.168.2.1', false ),
@@ -43,17 +34,14 @@ class ApiQueryUserContribsTest extends ApiTestCase {
 
        /**
         * @dataProvider provideSorting
-        * @param int $stage SCHEMA_COMPAT contants for $wgActorTableSchemaMigrationStage
         * @param array $params Extra parameters for the query
         * @param bool $reverse Reverse order?
         * @param int $revs Number of revisions to expect
         */
-       public function testSorting( $stage, $params, $reverse, $revs ) {
+       public function testSorting( $params, $reverse, $revs ) {
                // FIXME: fails under sqlite
                $this->markTestSkippedIfDbType( 'sqlite' );
 
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
-
                if ( isset( $params['ucuserids'] ) ) {
                        $params['ucuserids'] = implode( '|', array_map( 'User::idFromName', $params['ucuserids'] ) );
                }
@@ -116,38 +104,23 @@ class ApiQueryUserContribsTest extends ApiTestCase {
                $users2 = [ __CLASS__ . ' A', __CLASS__ . ' B', __CLASS__ . ' D' ];
                $ips = [ '192.168.2.1', '192.168.2.2', '192.168.2.3', '192.168.2.4' ];
 
-               foreach (
-                       [
-                               'old' => SCHEMA_COMPAT_OLD,
-                               'read old' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-                               'read new' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
-                               'new' => SCHEMA_COMPAT_NEW,
-                       ] as $stageName => $stage
-               ) {
-                       foreach ( [ false, true ] as $reverse ) {
-                               $name = $stageName . ( $reverse ? ', reverse' : '' );
-                               yield "Named users, $name" => [ $stage, [ 'ucuser' => $users ], $reverse, 9 ];
-                               yield "Named users including a no-edit user, $name" => [
-                                       $stage, [ 'ucuser' => $users2 ], $reverse, 6
-                               ];
-                               yield "IP users, $name" => [ $stage, [ 'ucuser' => $ips ], $reverse, 9 ];
-                               yield "All users, $name" => [
-                                       $stage, [ 'ucuser' => array_merge( $users, $ips ) ], $reverse, 18
-                               ];
-                               yield "User IDs, $name" => [ $stage, [ 'ucuserids' => $users ], $reverse, 9 ];
-                               yield "Users by prefix, $name" => [ $stage, [ 'ucuserprefix' => __CLASS__ ], $reverse, 9 ];
-                               yield "IPs by prefix, $name" => [ $stage, [ 'ucuserprefix' => '192.168.2.' ], $reverse, 9 ];
-                       }
+               foreach ( [ false, true ] as $reverse ) {
+                       $name = ( $reverse ? ', reverse' : '' );
+                       yield "Named users, $name" => [ [ 'ucuser' => $users ], $reverse, 9 ];
+                       yield "Named users including a no-edit user, $name" => [
+                               [ 'ucuser' => $users2 ], $reverse, 6
+                       ];
+                       yield "IP users, $name" => [ [ 'ucuser' => $ips ], $reverse, 9 ];
+                       yield "All users, $name" => [
+                               [ 'ucuser' => array_merge( $users, $ips ) ], $reverse, 18
+                       ];
+                       yield "User IDs, $name" => [ [ 'ucuserids' => $users ], $reverse, 9 ];
+                       yield "Users by prefix, $name" => [ [ 'ucuserprefix' => __CLASS__ ], $reverse, 9 ];
+                       yield "IPs by prefix, $name" => [ [ 'ucuserprefix' => '192.168.2.' ], $reverse, 9 ];
                }
        }
 
-       /**
-        * @dataProvider provideInterwikiUser
-        * @param int $stage SCHEMA_COMPAT constants for $wgActorTableSchemaMigrationStage
-        */
-       public function testInterwikiUser( $stage ) {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
-
+       public function testInterwikiUser() {
                $params = [
                        'action' => 'query',
                        'list' => 'usercontribs',
@@ -174,13 +147,4 @@ class ApiQueryUserContribsTest extends ApiTestCase {
                $this->assertSame( $sorted, $ids, "IDs are sorted" );
        }
 
-       public static function provideInterwikiUser() {
-               return [
-                       'old' => [ SCHEMA_COMPAT_OLD ],
-                       'read old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
-                       'read new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
-                       'new' => [ SCHEMA_COMPAT_NEW ],
-               ];
-       }
-
 }
index 1016f28..c789e83 100644 (file)
@@ -32,6 +32,7 @@ use Wikimedia\Rdbms\LoadBalancer;
 use Wikimedia\Rdbms\ChronologyProtector;
 use Wikimedia\Rdbms\MySQLMasterPos;
 use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\LoadMonitorNull;
 
 /**
  * @group Database
@@ -110,7 +111,6 @@ class LBFactoryTest extends MediaWikiTestCase {
                $this->assertSame( $factory->getLocalDomainID(), $factory->resolveDomainID( false ) );
 
                $factory->shutdown();
-               $lb->closeAll();
        }
 
        public function testLBFactorySimpleServers() {
@@ -160,7 +160,6 @@ class LBFactoryTest extends MediaWikiTestCase {
                        'cluster master set' );
 
                $factory->shutdown();
-               $lb->closeAll();
        }
 
        public function testLBFactoryMultiConns() {
index 062087d..3b30d7e 100644 (file)
@@ -942,8 +942,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        "$base/unittest-cont1/e/fileB.a",
                        "$base/unittest-cont1/e/fileC.a"
                ];
-               $createOps = [];
-               $purgeOps = [];
+               $createOps = $copyOps = $moveOps = $deleteOps = [];
                foreach ( $files as $path ) {
                        $status = $this->prepare( [ 'dir' => dirname( $path ) ] );
                        $this->assertGoodStatus( $status,
@@ -951,10 +950,21 @@ class FileBackendTest extends MediaWikiTestCase {
                        $createOps[] = [ 'op' => 'create', 'dst' => $path, 'content' => mt_rand( 0, 50000 ) ];
                        $copyOps[] = [ 'op' => 'copy', 'src' => $path, 'dst' => "$path-2" ];
                        $moveOps[] = [ 'op' => 'move', 'src' => "$path-2", 'dst' => "$path-3" ];
-                       $purgeOps[] = [ 'op' => 'delete', 'src' => $path ];
-                       $purgeOps[] = [ 'op' => 'delete', 'src' => "$path-3" ];
+                       $moveOps[] = [
+                               'op' => 'move',
+                               'src' => "$path-nothing",
+                               'dst' => "$path-nowhere",
+                               'ignoreMissingSource' => true
+                       ];
+                       $deleteOps[] = [ 'op' => 'delete', 'src' => $path ];
+                       $deleteOps[] = [ 'op' => 'delete', 'src' => "$path-3" ];
+                       $deleteOps[] = [
+                               'op' => 'delete',
+                               'src' => "$path-gone",
+                               'ignoreMissingSource' => true
+                       ];
                }
-               $purgeOps[] = [ 'op' => 'null' ];
+               $deleteOps[] = [ 'op' => 'null' ];
 
                $this->assertGoodStatus(
                        $this->backend->doQuickOperations( $createOps ),
@@ -995,7 +1005,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        "File {$files[0]} still exists." );
 
                $this->assertGoodStatus(
-                       $this->backend->doQuickOperations( $purgeOps ),
+                       $this->backend->doQuickOperations( $deleteOps ),
                        "Quick deletion of source files succeeded ($backendName)." );
                foreach ( $files as $file ) {
                        $this->assertFalse( $this->backend->fileExists( [ 'src' => $file ] ),
index 04dfa4e..f0205b8 100644 (file)
@@ -5,13 +5,13 @@ namespace Wikimedia\Tests\Message;
 use Wikimedia\Message\ListType;
 use Wikimedia\Message\MessageValue;
 use Wikimedia\Message\ParamType;
-use Wikimedia\Message\TextParam;
+use Wikimedia\Message\ScalarParam;
 use MediaWikiTestCase;
 
 /**
  * @covers \Wikimedia\Message\MessageValue
  * @covers \Wikimedia\Message\ListParam
- * @covers \Wikimedia\Message\TextParam
+ * @covers \Wikimedia\Message\ScalarParam
  * @covers \Wikimedia\Message\MessageParam
  */
 class MessageValueTest extends MediaWikiTestCase {
@@ -26,7 +26,7 @@ class MessageValueTest extends MediaWikiTestCase {
                                '<message key="key"><text>a</text></message>'
                        ],
                        [
-                               [ new TextParam( ParamType::BITRATE, 100 ) ],
+                               [ new ScalarParam( ParamType::BITRATE, 100 ) ],
                                '<message key="key"><bitrate>100</bitrate></message>'
                        ],
                ];
@@ -46,7 +46,7 @@ class MessageValueTest extends MediaWikiTestCase {
        public function testParams() {
                $mv = new MessageValue( 'key' );
                $mv->params( 1, 'x' );
-               $mv2 = $mv->params( new TextParam( ParamType::BITRATE, 100 ) );
+               $mv2 = $mv->params( new ScalarParam( ParamType::BITRATE, 100 ) );
                $this->assertSame(
                        '<message key="key"><text>1</text><text>x</text><bitrate>100</bitrate></message>',
                        $mv->dump() );
@@ -76,10 +76,11 @@ class MessageValueTest extends MediaWikiTestCase {
 
        public function testTextParams() {
                $mv = new MessageValue( 'key' );
-               $mv2 = $mv->textParams( 'a', 'b' );
+               $mv2 = $mv->textParams( 'a', 'b', new MessageValue( 'key2' ) );
                $this->assertSame( '<message key="key">' .
                        '<text>a</text>' .
                        '<text>b</text>' .
+                       '<text><message key="key2"></message></text>' .
                        '</message>',
                        $mv->dump() );
                $this->assertSame( $mv, $mv2 );
index 58f6c05..4419533 100644 (file)
@@ -2239,7 +2239,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                } catch ( DBUnexpectedError $ex ) {
                        $this->assertSame(
                                'Wikimedia\Rdbms\Database::close: ' .
-                               'mass commit/rollback of peer transaction required (DBO_TRX set)',
+                               'expected mass rollback of all peer transactions (DBO_TRX set)',
                                $ex->getMessage()
                        );
                }
index b183cde..a7040e0 100644 (file)
@@ -29,18 +29,12 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
         * @param array $selectFields
         * @param string[]|null $row
         * @param string[]|null $expectedFields
-        * @param int $actorMigration
         */
        public function testNewFromId( $id,
                array $selectFields,
                array $row = null,
-               array $expectedFields = null,
-               $actorMigration
+               array $expectedFields = null
        ) {
-               $this->setMwGlobals( [
-                       'wgActorTableSchemaMigrationStage' => $actorMigration,
-               ] );
-
                $row = $row ? (object)$row : null;
                $db = $this->getMock( IDatabase::class );
                $db->expects( self::once() )
@@ -67,36 +61,6 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
        }
 
        public function provideNewFromId() {
-               $oldTables = [
-                       'tables' => [
-                               'logging', 'user',
-                               'comment_log_comment' => 'comment',
-                       ],
-                       'fields' => [
-                               'log_id',
-                               'log_type',
-                               'log_action',
-                               'log_timestamp',
-                               'log_namespace',
-                               'log_title',
-                               'log_params',
-                               'log_deleted',
-                               'user_id',
-                               'user_name',
-                               'user_editcount',
-                               'log_comment_text' => 'comment_log_comment.comment_text',
-                               'log_comment_data' => 'comment_log_comment.comment_data',
-                               'log_comment_cid' => 'comment_log_comment.comment_id',
-                               'log_user' => 'log_user',
-                               'log_user_text' => 'log_user_text',
-                               'log_actor' => 'NULL',
-                       ],
-                       'options' => [],
-                       'join_conds' => [
-                               'user' => [ 'LEFT JOIN', 'user_id=log_user' ],
-                               'comment_log_comment' => [ 'JOIN', 'comment_log_comment.comment_id = log_comment_id' ],
-                       ],
-               ];
                $newTables = [
                        'tables' => [
                                'logging',
@@ -133,22 +97,20 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
                return [
                        [
                                0,
-                               $oldTables + [ 'conds' => [ 'log_id' => 0 ] ],
-                               null,
+                               $newTables + [ 'conds' => [ 'log_id' => 0 ] ],
                                null,
-                               SCHEMA_COMPAT_OLD,
+                               null
                        ],
                        [
                                123,
-                               $oldTables + [ 'conds' => [ 'log_id' => 123 ] ],
+                               $newTables + [ 'conds' => [ 'log_id' => 123 ] ],
                                [
                                        'log_id' => 123,
                                        'log_type' => 'foobarize',
                                        'log_comment_text' => 'test!',
                                        'log_comment_data' => null,
                                ],
-                               [ 'type' => 'foobarize', 'comment' => 'test!' ],
-                               SCHEMA_COMPAT_OLD,
+                               [ 'type' => 'foobarize', 'comment' => 'test!' ]
                        ],
                        [
                                567,
@@ -159,8 +121,7 @@ class DatabaseLogEntryTest extends MediaWikiTestCase {
                                        'log_comment_text' => 'test!',
                                        'log_comment_data' => null,
                                ],
-                               [ 'type' => 'foobarize', 'comment' => 'test!' ],
-                               SCHEMA_COMPAT_NEW,
+                               [ 'type' => 'foobarize', 'comment' => 'test!' ]
                        ],
                ];
        }
index bcbc1ed..90b118c 100644 (file)
@@ -84,7 +84,6 @@ abstract class PageArchiveTestBase extends MediaWikiTestCase {
                $this->tablesUsed += $this->getMcrTablesToReset();
 
                $this->setMwGlobals( [
-                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                        'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
                        'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
                ] );
index 5de1b0c..106cca3 100644 (file)
@@ -19,7 +19,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase {
                $path = __DIR__ . '/doesnotexist.json';
                $this->setExpectedException(
                        Exception::class,
-                       "$path does not exist!"
+                       "file $path"
                );
                $registry->queue( $path );
        }
index cd0867d..b2f525d 100644 (file)
@@ -259,14 +259,14 @@ class SessionManagerTest extends MediaWikiTestCase {
                try {
                        $manager->getSessionForRequest( $request );
                        $this->fail( 'Expcected exception not thrown' );
-               } catch ( \OverflowException $ex ) {
+               } catch ( SessionOverflowException $ex ) {
                        $this->assertStringStartsWith(
                                'Multiple sessions for this request tied for top priority: ',
                                $ex->getMessage()
                        );
-                       $this->assertCount( 2, $ex->sessionInfos );
-                       $this->assertContains( $request->info1, $ex->sessionInfos );
-                       $this->assertContains( $request->info2, $ex->sessionInfos );
+                       $this->assertCount( 2, $ex->getSessionInfos() );
+                       $this->assertContains( $request->info1, $ex->getSessionInfos() );
+                       $this->assertContains( $request->info2, $ex->getSessionInfos() );
                }
                $this->assertFalse( $request->unpersist1 );
                $this->assertFalse( $request->unpersist2 );
index 68433c6..ff3e714 100644 (file)
@@ -224,8 +224,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testRcHidemyselfFilter() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
@@ -253,41 +251,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testRcHidemyselfFilter_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $user = $this->getTestUser()->getUser();
-               $user->getActorId( wfGetDB( DB_MASTER ) );
-               $this->assertConditions(
-                       [ # expected
-                               "NOT((rc_user = '{$user->getId()}'))",
-                       ],
-                       [
-                               'hidemyself' => 1,
-                       ],
-                       "rc conditions: hidemyself=1 (logged in)",
-                       $user
-               );
-
-               $user = User::newFromName( '10.11.12.13', false );
-               $id = $user->getActorId( wfGetDB( DB_MASTER ) );
-               $this->assertConditions(
-                       [ # expected
-                               "NOT((rc_user_text = '10.11.12.13'))",
-                       ],
-                       [
-                               'hidemyself' => 1,
-                       ],
-                       "rc conditions: hidemyself=1 (anon)",
-                       $user
-               );
-       }
-
        public function testRcHidebyothersFilter() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
@@ -315,38 +279,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testRcHidebyothersFilter_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $user = $this->getTestUser()->getUser();
-               $user->getActorId( wfGetDB( DB_MASTER ) );
-               $this->assertConditions(
-                       [ # expected
-                               "(rc_user_text = '{$user->getName()}')",
-                       ],
-                       [
-                               'hidebyothers' => 1,
-                       ],
-                       "rc conditions: hidebyothers=1 (logged in)",
-                       $user
-               );
-
-               $user = User::newFromName( '10.11.12.13', false );
-               $id = $user->getActorId( wfGetDB( DB_MASTER ) );
-               $this->assertConditions(
-                       [ # expected
-                               "(rc_user_text = '10.11.12.13')",
-                       ],
-                       [
-                               'hidebyothers' => 1,
-                       ],
-                       "rc conditions: hidebyothers=1 (anon)",
-                       $user
-               );
-       }
-
        public function testRcHidepageedits() {
                $this->assertConditions(
                        [ # expected
@@ -550,8 +482,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelAllExperienceLevels() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
                $this->assertConditions(
                        [
                                # expected
@@ -564,26 +494,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testFilterUserExpLevelAllExperienceLevels_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $this->assertConditions(
-                       [
-                               # expected
-                               'rc_user != 0',
-                       ],
-                       [
-                               'userExpLevel' => 'newcomer;learner;experienced',
-                       ],
-                       "rc conditions: userExpLevel=newcomer;learner;experienced"
-               );
-       }
-
-       public function testFilterUserExpLevelRegistrered() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+       public function testFilterUserExpLevelRegistered() {
                $this->assertConditions(
                        [
                                # expected
@@ -596,26 +507,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testFilterUserExpLevelRegistrered_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $this->assertConditions(
-                       [
-                               # expected
-                               'rc_user != 0',
-                       ],
-                       [
-                               'userExpLevel' => 'registered',
-                       ],
-                       "rc conditions: userExpLevel=registered"
-               );
-       }
-
-       public function testFilterUserExpLevelUnregistrered() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+       public function testFilterUserExpLevelUnregistered() {
                $this->assertConditions(
                        [
                                # expected
@@ -628,26 +520,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testFilterUserExpLevelUnregistrered_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $this->assertConditions(
-                       [
-                               # expected
-                               'rc_user = 0',
-                       ],
-                       [
-                               'userExpLevel' => 'unregistered',
-                       ],
-                       "rc conditions: userExpLevel=unregistered"
-               );
-       }
-
-       public function testFilterUserExpLevelRegistreredOrLearner() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+       public function testFilterUserExpLevelRegisteredOrLearner() {
                $this->assertConditions(
                        [
                                # expected
@@ -660,26 +533,7 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testFilterUserExpLevelRegistreredOrLearner_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $this->assertConditions(
-                       [
-                               # expected
-                               'rc_user != 0',
-                       ],
-                       [
-                               'userExpLevel' => 'registered;learner',
-                       ],
-                       "rc conditions: userExpLevel=registered;learner"
-               );
-       }
-
-       public function testFilterUserExpLevelUnregistreredOrExperienced() {
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-
+       public function testFilterUserExpLevelUnregisteredOrExperienced() {
                $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
 
                $this->assertRegExp(
@@ -690,21 +544,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
        }
 
-       public function testFilterUserExpLevelUnregistreredOrExperienced_old() {
-               $this->setMwGlobals(
-                       'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
-               );
-
-               $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
-
-               $this->assertRegExp(
-                       '/\(rc_user = 0\) OR '
-                               . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
-                       reset( $conds ),
-                       "rc conditions: userExpLevel=unregistered;experienced"
-               );
-       }
-
        public function testFilterUserExpLevel() {
                $now = time();
                $this->setMwGlobals( [
index c0376ad..a5d5946 100644 (file)
@@ -60,6 +60,10 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
                        'callback array' => [
                                [ 'SpecialPageTestHelper', 'newSpecialAllPages' ],
                                false
+                       ],
+                       'object factory spec' => [
+                               [ 'class' => SpecialAllPages::class ],
+                               false
                        ]
                ];
        }
@@ -264,4 +268,29 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
                $this->assertTrue( $called, 'Recursive call succeeded' );
        }
 
+       /**
+        * @covers \MediaWiki\Special\SpecialPageFactory::getPage
+        */
+       public function testSpecialPageCreationThatRequiresService() {
+               $type = null;
+
+               $this->setMwGlobals( 'wgSpecialPages',
+                       [ 'TestPage' => [
+                               'factory' => function ( $spf ) use ( &$type ) {
+                                       $type = get_class( $spf );
+
+                                       return new class() extends SpecialPage {
+
+                                       };
+                               },
+                               'services' => [
+                                       'SpecialPageFactory'
+                               ]
+                       ] ]
+               );
+
+               SpecialPageFactory::getPage( 'TestPage' );
+
+               $this->assertEquals( \MediaWiki\Special\SpecialPageFactory::class, $type );
+       }
 }
index 58f83de..a95d43c 100644 (file)
@@ -22,7 +22,7 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase {
 
        /** List query pages that can not be tested automatically */
        protected $manualTest = [
-               LinkSearchPage::class
+               SpecialLinkSearch::class
        ];
 
        /**
index 4ecb813..08225a6 100644 (file)
@@ -2,15 +2,15 @@
 
 /**
  * @group Database
- * @covers MIMEsearchPage
+ * @covers SpecialMIMESearch
  */
 class SpecialMIMESearchTest extends MediaWikiTestCase {
 
-       /** @var MIMEsearchPage */
+       /** @var SpecialMIMESearch */
        private $page;
 
        function setUp() {
-               $this->page = new MIMEsearchPage;
+               $this->page = new SpecialMIMESearch;
                $context = new RequestContext();
                $context->setTitle( Title::makeTitle( NS_SPECIAL, 'MIMESearch' ) );
                $context->setRequest( new FauxRequest() );
diff --git a/tests/phpunit/includes/specials/SpecialShortPagesTest.php b/tests/phpunit/includes/specials/SpecialShortPagesTest.php
new file mode 100644 (file)
index 0000000..8891d0d
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Test class for SpecialShortPages class
+ *
+ * @since 1.30
+ *
+ * @license GPL-2.0-or-later
+ */
+class SpecialShortPagesTest extends MediaWikiTestCase {
+
+       /**
+        * @dataProvider provideGetQueryInfoRespectsContentNs
+        * @covers SpecialShortPages::getQueryInfo()
+        */
+       public function testGetQueryInfoRespectsContentNS( $contentNS, $blacklistNS, $expectedNS ) {
+               $this->setMwGlobals( [
+                       'wgShortPagesNamespaceBlacklist' => $blacklistNS,
+                       'wgContentNamespaces' => $contentNS
+               ] );
+               $this->setTemporaryHook( 'ShortPagesQuery', function () {
+                       // empty hook handler
+               } );
+
+               $page = new SpecialShortPages();
+               $queryInfo = $page->getQueryInfo();
+
+               $this->assertArrayHasKey( 'conds', $queryInfo );
+               $this->assertArrayHasKey( 'page_namespace', $queryInfo[ 'conds' ] );
+               $this->assertEquals( $expectedNS, $queryInfo[ 'conds' ][ 'page_namespace' ] );
+       }
+
+       public function provideGetQueryInfoRespectsContentNs() {
+               return [
+                       [ [ NS_MAIN, NS_FILE ], [], [ NS_MAIN, NS_FILE ] ],
+                       [ [ NS_MAIN, NS_TALK ], [ NS_FILE ], [ NS_MAIN, NS_TALK ] ],
+                       [ [ NS_MAIN, NS_FILE ], [ NS_FILE ], [ NS_MAIN ] ],
+                       // NS_MAIN namespace is always forced
+                       [ [], [ NS_FILE ], [ NS_MAIN ] ]
+               ];
+       }
+
+}
diff --git a/tests/phpunit/includes/specials/SpecialShortpagesTest.php b/tests/phpunit/includes/specials/SpecialShortpagesTest.php
deleted file mode 100644 (file)
index 236c5c4..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-/**
- * Test class for SpecialShortpages class
- *
- * @since 1.30
- *
- * @license GPL-2.0-or-later
- */
-class SpecialShortpagesTest extends MediaWikiTestCase {
-
-       /**
-        * @dataProvider provideGetQueryInfoRespectsContentNs
-        * @covers ShortPagesPage::getQueryInfo()
-        */
-       public function testGetQueryInfoRespectsContentNS( $contentNS, $blacklistNS, $expectedNS ) {
-               $this->setMwGlobals( [
-                       'wgShortPagesNamespaceBlacklist' => $blacklistNS,
-                       'wgContentNamespaces' => $contentNS
-               ] );
-               $this->setTemporaryHook( 'ShortPagesQuery', function () {
-                       // empty hook handler
-               } );
-
-               $page = new ShortPagesPage();
-               $queryInfo = $page->getQueryInfo();
-
-               $this->assertArrayHasKey( 'conds', $queryInfo );
-               $this->assertArrayHasKey( 'page_namespace', $queryInfo[ 'conds' ] );
-               $this->assertEquals( $expectedNS, $queryInfo[ 'conds' ][ 'page_namespace' ] );
-       }
-
-       public function provideGetQueryInfoRespectsContentNs() {
-               return [
-                       [ [ NS_MAIN, NS_FILE ], [], [ NS_MAIN, NS_FILE ] ],
-                       [ [ NS_MAIN, NS_TALK ], [ NS_FILE ], [ NS_MAIN, NS_TALK ] ],
-                       [ [ NS_MAIN, NS_FILE ], [ NS_FILE ], [ NS_MAIN ] ],
-                       // NS_MAIN namespace is always forced
-                       [ [], [ NS_FILE ], [ NS_MAIN ] ]
-               ];
-       }
-
-}
diff --git a/tests/phpunit/includes/specials/SpecialUncategorizedCategoriesTest.php b/tests/phpunit/includes/specials/SpecialUncategorizedCategoriesTest.php
new file mode 100644 (file)
index 0000000..daccd27
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Tests for Special:UncategorizedCategories
+ */
+class SpecialUncategorizedCategoriesTest extends MediaWikiTestCase {
+       /**
+        * @dataProvider provideTestGetQueryInfoData
+        * @covers SpecialUncategorizedCategories::getQueryInfo
+        */
+       public function testGetQueryInfo( $msgContent, $expected ) {
+               $msg = new RawMessage( $msgContent );
+               $mockContext = $this->getMockBuilder( RequestContext::class )->getMock();
+               $mockContext->method( 'msg' )->willReturn( $msg );
+               $special = new SpecialUncategorizedCategories();
+               $special->setContext( $mockContext );
+               $this->assertEquals( [
+                       'tables' => [
+                               0 => 'page',
+                               1 => 'categorylinks',
+                       ],
+                       'fields' => [
+                               'namespace' => 'page_namespace',
+                               'title' => 'page_title',
+                               'value' => 'page_title',
+                       ],
+                       'conds' => [
+                               0 => 'cl_from IS NULL',
+                               'page_namespace' => 14,
+                               'page_is_redirect' => 0,
+                       ] + $expected,
+                       'join_conds' => [
+                               'categorylinks' => [
+                                       0 => 'LEFT JOIN',
+                                       1 => 'cl_from = page_id',
+                               ],
+                       ],
+               ], $special->getQueryInfo() );
+       }
+
+       public function provideTestGetQueryInfoData() {
+               return [
+                       [
+                               "* Stubs\n* Test\n* *\n* * test123",
+                               [ 1 => "page_title not in ( 'Stubs','Test','*','*_test123' )" ]
+                       ],
+                       [
+                               "Stubs\n* Test\n* *\n* * test123",
+                               [ 1 => "page_title not in ( 'Test','*','*_test123' )" ]
+                       ],
+                       [
+                               "* StubsTest\n* *\n* * test123",
+                               [ 1 => "page_title not in ( 'StubsTest','*','*_test123' )" ]
+                       ],
+                       [ "", [] ],
+                       [ "\n\n\n", [] ],
+                       [ "\n", [] ],
+                       [ "Test\n*Test2", [ 1 => "page_title not in ( 'Test2' )" ] ],
+                       [ "Test", [] ],
+                       [ "*Test\nTest2", [ 1 => "page_title not in ( 'Test' )" ] ],
+                       [ "Test\nTest2", [] ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/specials/UncategorizedCategoriesPageTest.php b/tests/phpunit/includes/specials/UncategorizedCategoriesPageTest.php
deleted file mode 100644 (file)
index 80bd365..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-/**
- * Tests for Special:Uncategorizedcategories
- */
-class UncategorizedCategoriesPageTest extends MediaWikiTestCase {
-       /**
-        * @dataProvider provideTestGetQueryInfoData
-        * @covers UncategorizedCategoriesPage::getQueryInfo
-        */
-       public function testGetQueryInfo( $msgContent, $expected ) {
-               $msg = new RawMessage( $msgContent );
-               $mockContext = $this->getMockBuilder( RequestContext::class )->getMock();
-               $mockContext->method( 'msg' )->willReturn( $msg );
-               $special = new UncategorizedCategoriesPage();
-               $special->setContext( $mockContext );
-               $this->assertEquals( [
-                       'tables' => [
-                               0 => 'page',
-                               1 => 'categorylinks',
-                       ],
-                       'fields' => [
-                               'namespace' => 'page_namespace',
-                               'title' => 'page_title',
-                               'value' => 'page_title',
-                       ],
-                       'conds' => [
-                               0 => 'cl_from IS NULL',
-                               'page_namespace' => 14,
-                               'page_is_redirect' => 0,
-                       ] + $expected,
-                       'join_conds' => [
-                               'categorylinks' => [
-                                       0 => 'LEFT JOIN',
-                                       1 => 'cl_from = page_id',
-                               ],
-                       ],
-               ], $special->getQueryInfo() );
-       }
-
-       public function provideTestGetQueryInfoData() {
-               return [
-                       [
-                               "* Stubs\n* Test\n* *\n* * test123",
-                               [ 1 => "page_title not in ( 'Stubs','Test','*','*_test123' )" ]
-                       ],
-                       [
-                               "Stubs\n* Test\n* *\n* * test123",
-                               [ 1 => "page_title not in ( 'Test','*','*_test123' )" ]
-                       ],
-                       [
-                               "* StubsTest\n* *\n* * test123",
-                               [ 1 => "page_title not in ( 'StubsTest','*','*_test123' )" ]
-                       ],
-                       [ "", [] ],
-                       [ "\n\n\n", [] ],
-                       [ "\n", [] ],
-                       [ "Test\n*Test2", [ 1 => "page_title not in ( 'Test2' )" ] ],
-                       [ "Test", [] ],
-                       [ "*Test\nTest2", [ 1 => "page_title not in ( 'Test' )" ] ],
-                       [ "Test\nTest2", [] ],
-               ];
-       }
-}
index 2d87c78..3005ae9 100644 (file)
@@ -31,7 +31,6 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgGroupPermissions' => [],
                        'wgRevokePermissions' => [],
-                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                ] );
 
                $this->setUpPermissionGlobals();
@@ -1075,83 +1074,6 @@ class UserTest extends MediaWikiTestCase {
                        'User::newFromActorId works for an anonymous user' );
        }
 
-       /**
-        * Actor tests with SCHEMA_COMPAT_READ_OLD
-        *
-        * The only thing different from testActorId() is the behavior if the actor
-        * row doesn't exist in the DB, since with SCHEMA_COMPAT_READ_NEW that
-        * situation can't happen. But we copy all the other tests too just for good measure.
-        *
-        * @covers User::newFromActorId
-        */
-       public function testActorId_old() {
-               $this->setMwGlobals( [
-                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
-               ] );
-
-               $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
-               $this->hideDeprecated( 'User::selectFields' );
-
-               // Newly-created user has an actor ID
-               $user = User::createNew( 'UserTestActorIdOld1' );
-               $id = $user->getId();
-               $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
-
-               $user = User::newFromName( 'UserTestActorIdOld2' );
-               $user->addToDatabase();
-               $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
-
-               $user = User::newFromName( 'UserTestActorIdOld1' );
-               $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
-
-               $user = User::newFromId( $id );
-               $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
-
-               $user2 = User::newFromActorId( $user->getActorId() );
-               $this->assertEquals( $user->getId(), $user2->getId(),
-                       'User::newFromActorId works for an existing user' );
-
-               $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
-               $user = User::newFromRow( $row );
-               $this->assertTrue( $user->getActorId() > 0,
-                       'Actor ID can be retrieved for user loaded with User::selectFields()' );
-
-               $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
-               User::purge( $domain, $id );
-               // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
-
-               MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
-
-               $user = User::newFromId( $id );
-               $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );
-               $this->assertTrue( $user->getActorId( $this->db ) > 0, 'Actor ID can be created if none in db' );
-
-               $user->setName( 'UserTestActorIdOld4-renamed' );
-               $user->saveSettings();
-               $this->assertEquals(
-                       $user->getName(),
-                       $this->db->selectField(
-                               'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
-                       ),
-                       'User::saveSettings updates actor table for name change'
-               );
-
-               // For sanity
-               $ip = '192.168.12.34';
-               $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
-
-               $user = User::newFromName( $ip, false );
-               $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
-               $this->assertTrue( $user->getActorId( $this->db ) > 0,
-                       'Actor ID can be created for an anonymous user' );
-
-               $user = User::newFromName( $ip, false );
-               $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
-               $user2 = User::newFromActorId( $user->getActorId() );
-               $this->assertEquals( $user->getName(), $user2->getName(),
-                       'User::newFromActorId works for an anonymous user' );
-       }
-
        /**
         * @covers User::newFromAnyId
         */
diff --git a/tests/phpunit/maintenance/MWDoxygenFilterTest.php b/tests/phpunit/maintenance/MWDoxygenFilterTest.php
new file mode 100644 (file)
index 0000000..22b5938
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+/**
+* @covers MWDoxygenFilter
+ */
+class MWDoxygenFilterTest extends \PHPUnit\Framework\TestCase {
+
+       public static function provideFilter() {
+               yield 'No @var' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /** Some Words here */
+       protected $name;
+}
+CODE
+               ];
+
+               yield 'One-line var with type' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /** @var SomeType */
+       protected $name;
+}
+CODE
+                       , <<<'CODE'
+<?php class MyClass {
+       /**  */
+       protected SomeType $name;
+}
+CODE
+               ];
+
+               yield 'One-line var with type and description' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /** @var SomeType Some description */
+       protected $name;
+}
+CODE
+                       , <<<'CODE'
+<?php class MyClass {
+       /**  Some description */
+       protected SomeType $name;
+}
+CODE
+               ];
+
+               yield 'One-line var with type and description that starts like a variable name' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /** @var array $_GET data from some thing */
+       protected $name;
+}
+CODE
+                       , <<<'CODE'
+<?php class MyClass {
+       /**  $_GET data from some thing */
+       protected array $name;
+}
+CODE
+               ];
+
+               yield 'One-line var with type, name, and description' => [
+                       // In this full form, Doxygen understands it just fine.
+                       // No changes made.
+                       <<<'CODE'
+<?php class MyClass {
+       /** @var SomeType $name Some description */
+       protected $name;
+}
+CODE
+               ];
+
+               yield 'Multi-line var with type' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /**
+        * @var SomeType
+        */
+       protected $name;
+}
+CODE
+                       , <<<'CODE'
+<?php class MyClass {
+       /**
+        * 
+        */
+       protected SomeType $name;
+}
+CODE
+               ];
+
+               yield 'Multi-line var with type and description' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /**
+        * Some description
+        * @var SomeType
+        */
+       protected $name;
+}
+CODE
+                       , <<<'CODE'
+<?php class MyClass {
+       /**
+        * Some description
+        * 
+        */
+       protected SomeType $name;
+}
+CODE
+               ];
+
+               yield 'Multi-line var with type, name, and description' => [
+                       <<<'CODE'
+<?php class MyClass {
+       /**
+        * Some description
+        * @var SomeType $name
+        */
+       protected $name;
+}
+CODE
+                       , <<<'CODE'
+<?php class MyClass {
+       /**
+        * Some description
+        * @var SomeType $name
+        */
+       protected $name;
+}
+CODE
+               ];
+       }
+
+       /**
+        * @dataProvider provideFilter
+        */
+       public function testFilter( $source, $expected = null ) {
+               if ( $expected === null ) {
+                       $expected = $source;
+               }
+               $this->assertSame( $expected, MWDoxygenFilter::filter( $source ), 'Source code' );
+       }
+}
index b8d1383..cd0d7a5 100644 (file)
@@ -37,7 +37,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'patrol',
-                       'log_user' => 7251,
+                       'log_actor' => 7251,
                        'log_params' => '',
                        'log_timestamp' => $dbw->timestamp( '20041223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -49,7 +49,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'autopatrol',
-                       'log_user' => 7252,
+                       'log_actor' => 7252,
                        'log_params' => '',
                        'log_timestamp' => $dbw->timestamp( '20051223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -61,7 +61,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'block',
                        'log_action' => 'block',
-                       'log_user' => 7253,
+                       'log_actor' => 7253,
                        'log_params' => '',
                        'log_timestamp' => $dbw->timestamp( '20061223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -73,7 +73,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'patrol',
-                       'log_user' => 7253,
+                       'log_actor' => 7253,
                        'log_params' => 'nanana',
                        'log_timestamp' => $dbw->timestamp( '20061223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -85,7 +85,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'autopatrol',
-                       'log_user' => 7254,
+                       'log_actor' => 7254,
                        'log_params' => '',
                        'log_timestamp' => $dbw->timestamp( '20071223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -97,7 +97,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'patrol',
-                       'log_user' => 7255,
+                       'log_actor' => 7255,
                        'log_params' => serialize( [ '6::auto' => true ] ),
                        'log_timestamp' => $dbw->timestamp( '20081223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -109,7 +109,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'patrol',
-                       'log_user' => 7256,
+                       'log_actor' => 7256,
                        'log_params' => serialize( [ '6::auto' => false ] ),
                        'log_timestamp' => $dbw->timestamp( '20091223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -121,7 +121,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'patrol',
-                       'log_user' => 7257,
+                       'log_actor' => 7257,
                        'log_params' => "9227851\n0\n1",
                        'log_timestamp' => $dbw->timestamp( '20081223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -133,7 +133,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $logs[] = [
                        'log_type' => 'patrol',
                        'log_action' => 'patrol',
-                       'log_user' => 7258,
+                       'log_actor' => 7258,
                        'log_params' => "9227851\n0\n0",
                        'log_timestamp' => $dbw->timestamp( '20091223210426' ),
                        'log_namespace' => NS_MAIN,
@@ -149,47 +149,47 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'patrol',
-                               'log_user' => '7251',
+                               'log_actor' => '7251',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'autopatrol',
-                               'log_user' => '7252',
+                               'log_actor' => '7252',
                        ],
                        (object)[
                                'log_type' => 'block',
                                'log_action' => 'block',
-                               'log_user' => '7253',
+                               'log_actor' => '7253',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'patrol',
-                               'log_user' => '7253',
+                               'log_actor' => '7253',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'autopatrol',
-                               'log_user' => '7254',
+                               'log_actor' => '7254',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'patrol',
-                               'log_user' => '7255',
+                               'log_actor' => '7255',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'patrol',
-                               'log_user' => '7256',
+                               'log_actor' => '7256',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'patrol',
-                               'log_user' => '7257',
+                               'log_actor' => '7257',
                        ],
                        (object)[
                                'log_type' => 'patrol',
                                'log_action' => 'patrol',
-                               'log_user' => '7258',
+                               'log_actor' => '7258',
                        ],
                ];
 
@@ -266,7 +266,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
 
                $remainingLogs = wfGetDB( DB_REPLICA )->select(
                        [ 'logging' ],
-                       [ 'log_type', 'log_action', 'log_user' ],
+                       [ 'log_type', 'log_action', 'log_actor' ],
                        [],
                        __METHOD__,
                        [ 'ORDER BY' => 'log_id' ]
@@ -288,7 +288,7 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
 
                $remainingLogs = wfGetDB( DB_REPLICA )->select(
                        [ 'logging' ],
-                       [ 'log_type', 'log_action', 'log_user' ],
+                       [ 'log_type', 'log_action', 'log_actor' ],
                        [],
                        __METHOD__,
                        [ 'ORDER BY' => 'log_id' ]
@@ -297,12 +297,12 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
                $deleted = [
                        'log_type' => 'patrol',
                        'log_action' => 'autopatrol',
-                       'log_user' => '7254',
+                       'log_actor' => '7254',
                ];
                $notDeleted = [
                        'log_type' => 'patrol',
                        'log_action' => 'autopatrol',
-                       'log_user' => '7252',
+                       'log_actor' => '7252',
                ];
 
                $remainingLogs = array_map(
index 188629f..91652a2 100644 (file)
@@ -8,6 +8,10 @@ use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
 use MediaWiki\Rest\RequestData;
 use MediaWiki\Rest\ResponseFactory;
 use MediaWiki\Rest\Router;
+use MediaWiki\Rest\Validator\Validator;
+use Psr\Container\ContainerInterface;
+use Wikimedia\ObjectFactory;
+use User;
 
 /**
  * @covers \MediaWiki\Rest\Handler\HelloHandler
@@ -48,14 +52,21 @@ class HelloHandlerTest extends \MediaWikiUnitTestCase {
 
        /** @dataProvider provideTestViaRouter */
        public function testViaRouter( $requestInfo, $responseInfo ) {
+               $objectFactory = new ObjectFactory(
+                       $this->getMockForAbstractClass( ContainerInterface::class )
+               );
+
+               $request = new RequestData( $requestInfo );
                $router = new Router(
                        [ __DIR__ . '/../testRoutes.json' ],
                        [],
                        '/rest',
                        new EmptyBagOStuff(),
                        new ResponseFactory(),
-                       new StaticBasicAuthorizer() );
-               $request = new RequestData( $requestInfo );
+                       new StaticBasicAuthorizer(),
+                       $objectFactory,
+                       new Validator( $objectFactory, $request, new User )
+               );
                $response = $router->execute( $request );
                if ( isset( $responseInfo['statusCode'] ) ) {
                        $this->assertSame( $responseInfo['statusCode'], $response->getStatusCode() );
index cacccb9..e16ea25 100644 (file)
@@ -7,37 +7,48 @@ use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
 use MediaWiki\Rest\Handler;
 use MediaWiki\Rest\HttpException;
 use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\RequestInterface;
 use MediaWiki\Rest\ResponseFactory;
 use MediaWiki\Rest\Router;
+use MediaWiki\Rest\Validator\Validator;
+use Psr\Container\ContainerInterface;
+use Wikimedia\ObjectFactory;
+use User;
 
 /**
  * @covers \MediaWiki\Rest\Router
  */
 class RouterTest extends \MediaWikiUnitTestCase {
        /** @return Router */
-       private function createRouter( $authError = null ) {
+       private function createRouter( RequestInterface $request, $authError = null ) {
+               $objectFactory = new ObjectFactory(
+                       $this->getMockForAbstractClass( ContainerInterface::class )
+               );
                return new Router(
                        [ __DIR__ . '/testRoutes.json' ],
                        [],
                        '/rest',
                        new \EmptyBagOStuff(),
                        new ResponseFactory(),
-                       new StaticBasicAuthorizer( $authError ) );
+                       new StaticBasicAuthorizer( $authError ),
+                       $objectFactory,
+                       new Validator( $objectFactory, $request, new User )
+               );
        }
 
        public function testPrefixMismatch() {
-               $router = $this->createRouter();
                $request = new RequestData( [ 'uri' => new Uri( '/bogus' ) ] );
+               $router = $this->createRouter( $request );
                $response = $router->execute( $request );
                $this->assertSame( 404, $response->getStatusCode() );
        }
 
        public function testWrongMethod() {
-               $router = $this->createRouter();
                $request = new RequestData( [
                        'uri' => new Uri( '/rest/user/joe/hello' ),
                        'method' => 'OPTIONS'
                ] );
+               $router = $this->createRouter( $request );
                $response = $router->execute( $request );
                $this->assertSame( 405, $response->getStatusCode() );
                $this->assertSame( 'Method Not Allowed', $response->getReasonPhrase() );
@@ -45,8 +56,8 @@ class RouterTest extends \MediaWikiUnitTestCase {
        }
 
        public function testNoMatch() {
-               $router = $this->createRouter();
                $request = new RequestData( [ 'uri' => new Uri( '/rest/bogus' ) ] );
+               $router = $this->createRouter( $request );
                $response = $router->execute( $request );
                $this->assertSame( 404, $response->getStatusCode() );
                // TODO: add more information to the response body and test for its presence here
@@ -61,8 +72,8 @@ class RouterTest extends \MediaWikiUnitTestCase {
        }
 
        public function testException() {
-               $router = $this->createRouter();
                $request = new RequestData( [ 'uri' => new Uri( '/rest/mock/RouterTest/throw' ) ] );
+               $router = $this->createRouter( $request );
                $response = $router->execute( $request );
                $this->assertSame( 555, $response->getStatusCode() );
                $body = $response->getBody();
@@ -72,9 +83,9 @@ class RouterTest extends \MediaWikiUnitTestCase {
        }
 
        public function testBasicAccess() {
-               $router = $this->createRouter( 'test-error' );
                // Using the throwing handler is a way to assert that the handler is not executed
                $request = new RequestData( [ 'uri' => new Uri( '/rest/mock/RouterTest/throw' ) ] );
+               $router = $this->createRouter( $request, 'test-error' );
                $response = $router->execute( $request );
                $this->assertSame( 403, $response->getStatusCode() );
                $body = $response->getBody();
diff --git a/tests/phpunit/unit/includes/installer/SqliteInstallerTest.php b/tests/phpunit/unit/includes/installer/SqliteInstallerTest.php
new file mode 100644 (file)
index 0000000..19a2973
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @group sqlite
+ * @group Database
+ * @group medium
+ */
+class SqliteInstallerTest extends \MediaWikiUnitTestCase {
+       /**
+        * @covers SqliteInstaller::checkDataDir
+        */
+       public function testCheckDataDir() {
+               $method = new ReflectionMethod( SqliteInstaller::class, 'checkDataDir' );
+               $method->setAccessible( true );
+
+               # Test 1: Should return fatal Status if $dir exist and it un-writable
+               if ( ( isset( $_SERVER['USER'] ) && $_SERVER['USER'] !== 'root' ) && !wfIsWindows() ) {
+                       // We can't simulate this environment under Windows or login as root
+                       $dir = sys_get_temp_dir() . '/' . uniqid( 'MediaWikiTest' );
+                       mkdir( $dir, 0000 );
+                       /** @var Status $status */
+                       $status = $method->invoke( null, $dir );
+                       $this->assertFalse( $status->isGood() );
+                       $this->assertSame( 'config-sqlite-dir-unwritable', $status->getErrors()[0]['message'] );
+                       rmdir( $dir );
+               }
+
+               # Test 2: Should return fatal Status if $dir not exist and it parent also not exist
+               $dir = sys_get_temp_dir() . '/' . uniqid( 'MediaWikiTest' ) . '/' . uniqid( 'MediaWikiTest' );
+               $status = $method->invoke( null, $dir );
+               $this->assertFalse( $status->isGood() );
+
+               # Test 3: Should return good Status if $dir not exist and it parent writable
+               $dir = sys_get_temp_dir() . '/' . uniqid( 'MediaWikiTest' );
+               /** @var Status $status */
+               $status = $method->invoke( null, $dir );
+               $this->assertTrue( $status->isGood() );
+       }
+
+       /**
+        * @covers SqliteInstaller::createDataDir
+        */
+       public function testCreateDataDir() {
+               $method = new ReflectionMethod( SqliteInstaller::class, 'createDataDir' );
+               $method->setAccessible( true );
+
+               # Test 1: Should return fatal Status if $dir not exist and it parent un-writable
+               if ( ( isset( $_SERVER['USER'] ) && $_SERVER['USER'] !== 'root' ) && !wfIsWindows() ) {
+                       // We can't simulate this environment under Windows or login as root
+                       $random = uniqid( 'MediaWikiTest' );
+                       $dir = sys_get_temp_dir() . '/' . $random . '/' . uniqid( 'MediaWikiTest' );
+                       mkdir( sys_get_temp_dir() . "/$random", 0000 );
+                       /** @var Status $status */
+                       $status = $method->invoke( null, $dir );
+                       $this->assertFalse( $status->isGood() );
+                       $this->assertSame( 'config-sqlite-mkdir-error', $status->getErrors()[0]['message'] );
+                       rmdir( sys_get_temp_dir() . "/$random" );
+               }
+
+               # Test 2: Test .htaccess content after created successfully
+               $dir = sys_get_temp_dir() . '/' . uniqid( 'MediaWikiTest' );
+               $status = $method->invoke( null, $dir );
+               $this->assertTrue( $status->isGood() );
+               $this->assertSame( "Deny from all\n", file_get_contents( "$dir/.htaccess" ) );
+               unlink( "$dir/.htaccess" );
+               rmdir( $dir );
+       }
+}
index d55b603..cab6c3b 100644 (file)
@@ -60,7 +60,6 @@ return [
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js',
-                       'tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.String.byteLength.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.String.trimByteLength.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js',
@@ -102,7 +101,6 @@ return [
                        'tests/qunit/suites/resources/mediawiki/mediawiki.visibleTimeout.test.js',
                ],
                'dependencies' => [
-                       'jquery.accessKeyLabel',
                        'jquery.color',
                        'jquery.colorUtil',
                        'jquery.getAttrs',
@@ -116,7 +114,6 @@ return [
                        'mediawiki.ForeignApi.core',
                        'mediawiki.jqueryMsg',
                        'mediawiki.messagePoster',
-                       'mediawiki.RegExp',
                        'mediawiki.String',
                        'mediawiki.storage',
                        'mediawiki.Title',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js
deleted file mode 100644 (file)
index cde77e7..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-( function () {
-       QUnit.module( 'mediawiki.RegExp' );
-
-       QUnit.test( 'escape', function ( assert ) {
-               var specials, normal;
-
-               specials = [
-                       '\\',
-                       '{',
-                       '}',
-                       '(',
-                       ')',
-                       '[',
-                       ']',
-                       '|',
-                       '.',
-                       '?',
-                       '*',
-                       '+',
-                       '-',
-                       '^',
-                       '$'
-               ];
-
-               normal = [
-                       'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
-                       'abcdefghijklmnopqrstuvwxyz',
-                       '0123456789'
-               ].join( '' );
-
-               specials.forEach( function ( str ) {
-                       assert.propEqual( str.match( new RegExp( mw.RegExp.escape( str ) ) ), [ str ], 'Match ' + str );
-               } );
-
-               assert.strictEqual( mw.RegExp.escape( normal ), normal, 'Alphanumerals are left alone' );
-       } );
-
-}() );
index 17672db..4f61abd 100644 (file)
                        assert.strictEqual( util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] );
                } );
        } );
+
+       QUnit.test( 'escapeRegExp', function ( assert ) {
+               var specials, normal;
+
+               specials = [
+                       '\\',
+                       '{',
+                       '}',
+                       '(',
+                       ')',
+                       '[',
+                       ']',
+                       '|',
+                       '.',
+                       '?',
+                       '*',
+                       '+',
+                       '-',
+                       '^',
+                       '$'
+               ];
+
+               normal = [
+                       'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+                       'abcdefghijklmnopqrstuvwxyz',
+                       '0123456789'
+               ].join( '' );
+
+               specials.forEach( function ( str ) {
+                       assert.propEqual( str.match( new RegExp( mw.util.escapeRegExp( str ) ) ), [ str ], 'Match ' + str );
+               } );
+
+               assert.strictEqual( mw.util.escapeRegExp( normal ), normal, 'Alphanumerals are left alone' );
+       } );
 }() );
index 13dbc0e..5be1ed0 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -25,6 +25,7 @@ use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
 
 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
+define( 'MW_ENTRY_POINT', 'thumb' );
 require __DIR__ . '/includes/WebStart.php';
 
 // Don't use fancy MIME detection, just check the file extension for jpg/gif/png
@@ -35,7 +36,7 @@ if ( defined( 'THUMB_HANDLER' ) ) {
        wfThumbHandle404();
 } else {
        // Called directly, use $_GET params
-       wfStreamThumb( $wgRequest->getQueryValues() );
+       wfStreamThumb( $wgRequest->getQueryValuesOnly() );
 }
 
 $mediawiki = new MediaWiki();
index e2b165b..4e6bbd9 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 define( 'THUMB_HANDLER', true );
+define( 'MW_ENTRY_POINT', 'thumb_handler' );
 
 // Execute thumb.php, having set THUMB_HANDLER so that
 // it knows to extract params from a thumbnail file URL.