Merge "Pass the user and request into BlockManager::getUserBlock"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 11 Sep 2019 18:58:32 +0000 (18:58 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 11 Sep 2019 18:58:32 +0000 (18:58 +0000)
253 files changed:
.phpcs.xml
RELEASE-NOTES-1.34
autoload.php
composer.json
docs/hooks.txt
includes/ActorMigration.php
includes/DefaultSettings.php
includes/MovePage.php
includes/PHPVersionCheck.php
includes/Revision.php
includes/Revision/RevisionStore.php
includes/ServiceWiring.php
includes/Setup.php
includes/actions/InfoAction.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/ApiRevisionDelete.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/zh-hant.json
includes/auth/AuthenticationRequest.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/deferred/LinksUpdate.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/installer/CliInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/WebInstallerOptions.php
includes/installer/i18n/ia.json
includes/installer/i18n/ja.json
includes/installer/i18n/pl.json
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/filebackend/FSFileBackend.php
includes/libs/filebackend/FileBackendStore.php
includes/libs/filebackend/MemoryFileBackend.php
includes/libs/filebackend/fileophandle/FileBackendStoreOpHandle.php
includes/libs/filebackend/fsfile/FSFile.php
includes/libs/redis/RedisConnectionPool.php
includes/logging/ManualLogEntry.php
includes/page/WikiPage.php
includes/preferences/DefaultPreferencesFactory.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderLanguageDataModule.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/SpecialPagesWithProp.php
includes/specials/SpecialRedirect.php
includes/specials/SpecialRevisionDelete.php
includes/specials/SpecialRunJobs.php
includes/specials/SpecialShortPages.php [new file with mode: 0644]
includes/specials/SpecialShortpages.php [deleted file]
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/user/User.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
languages/i18n/cs.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/exif/pl.json
languages/i18n/fr.json
languages/i18n/gom-deva.json
languages/i18n/gom-latn.json
languages/i18n/hr.json
languages/i18n/ja.json
languages/i18n/mk.json
languages/i18n/pl.json
languages/i18n/qqq.json
languages/i18n/sv.json
languages/i18n/szl.json
languages/i18n/tt-cyrl.json
languages/i18n/zh-hant.json
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/dumpIterator.php
maintenance/getReplicaServer.php
maintenance/importDump.php
maintenance/includes/BackupDumper.php
maintenance/includes/MigrateActors.php
maintenance/install.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/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
resources/Resources.php
resources/src/mediawiki.RegExp.js [deleted file]
resources/src/mediawiki.Title/Title.js
resources/src/mediawiki.Title/phpCharToUpper.json
resources/src/mediawiki.util/util.js
tests/parser/ParserTestRunner.php
tests/phpunit/includes/ActorMigrationTest.php
tests/phpunit/includes/ActorMigrationTest.sql [new file with mode: 0644]
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/logging/DatabaseLogEntryTest.php
tests/phpunit/includes/page/PageArchiveTestBase.php
tests/phpunit/includes/session/SessionManagerTest.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.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/deleteAutoPatrolLogsTest.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 f77354a..09195e1 100644 (file)
@@ -91,6 +91,8 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
 * $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
@@ -121,6 +123,9 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
 * (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 ===
 
@@ -157,8 +162,15 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
 === 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 ===
@@ -178,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
@@ -375,7 +415,19 @@ because of Phabricator reports.
   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.
@@ -499,6 +551,18 @@ because of Phabricator reports.
 * 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 ===
 * …
index 0255a23..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',
@@ -866,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',
@@ -1001,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',
@@ -1330,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',
@@ -1357,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',
@@ -1365,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',
@@ -1372,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',
@@ -1428,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',
@@ -1516,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',
@@ -1528,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',
@@ -1596,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',
@@ -1704,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 a248c29..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.
@@ -3729,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 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 00708f4..47fd073 100644 (file)
@@ -4888,6 +4888,7 @@ $wgDefaultUserOptions = [
        'wllimit' => 250,
        'useeditwarning' => 1,
        'prefershttps' => 1,
+       'requireemail' => 0,
 ];
 
 /**
@@ -4957,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 }
 
 /************************************************************************//**
@@ -8609,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;
 
@@ -8979,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 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 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 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 3be84a5..c7ad75c 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 {
index 39f0c81..518531a 100644 (file)
@@ -808,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 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 f0e0077..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' );
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 1ee91c2..60b24f0 100644 (file)
@@ -38,12 +38,6 @@ class ApiRevisionDelete extends ApiBase {
                $user = $this->getUser();
                $this->checkUserRightsAny( RevisionDeleter::getRestriction( $params['type'] ) );
 
-               // @TODO Use PermissionManager::isBlockedFrom() instead.
-               $block = $user->getBlock();
-               if ( $block ) {
-                       $this->dieBlocked( $block );
-               }
-
                if ( !$params['ids'] ) {
                        $this->dieWithError( [ 'apierror-paramempty', 'ids' ], 'paramempty_ids' );
                }
@@ -97,6 +91,10 @@ class ApiRevisionDelete extends ApiBase {
                        $this->dieWithError( [ 'apierror-revdel-needtarget' ], 'needtarget' );
                }
 
+               if ( $this->getPermissionManager()->isBlockedFrom( $user, $targetObj ) ) {
+                       $this->dieBlocked( $user->getBlock() );
+               }
+
                $list = RevisionDeleter::createList(
                        $params['type'], $this->getContext(), $targetObj, $params['ids']
                );
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 628edfa..7c5d2b5 100644 (file)
        "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.",
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 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 168ae85..bc70d5e 100644 (file)
@@ -246,10 +246,11 @@ abstract class AuthenticationRequest {
        }
 
        /**
+        * Select a request by class name.
+        *
         * @codingStandardsIgnoreStart
-        * @template T
+        * @phan-template T
         * @codingStandardsIgnoreEnd
-        * Select a request by class name.
         * @param AuthenticationRequest[] $reqs
         * @param string $class Class name
         * @phan-param class-string<T> $class
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 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 7fcda4c..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(),
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 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 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 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 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 fc66bdb..f3f211e 100644 (file)
@@ -24,7 +24,8 @@
                        "Sethakill",
                        "Peter Bowman",
                        "Ankam",
-                       "Railfail536"
+                       "Railfail536",
+                       "Rail"
                ]
        },
        "config-desc": "Instalator MediaWiki",
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 5534cbd..9ed7ae3 100644 (file)
@@ -556,9 +556,7 @@ class FSFileBackend extends FileBackendStore {
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
                AtEase::suppressWarnings();
-               if ( is_dir( $dir ) ) {
-                       rmdir( $dir ); // remove directory if empty
-               }
+               rmdir( $dir ); // remove directory if empty
                AtEase::restoreWarnings();
 
                return $status;
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 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 eb645cc..343e35c 100644 (file)
@@ -90,6 +90,10 @@ class RedisConnectionPool implements LoggerAwareInterface {
                if ( !isset( $options['serializer'] ) || $options['serializer'] === 'php' ) {
                        $this->serializer = Redis::SERIALIZER_PHP;
                } elseif ( $options['serializer'] === 'igbinary' ) {
+                       if ( !defined( 'Redis::SERIALIZER_IGBINARY' ) ) {
+                               throw new InvalidArgumentException(
+                                       __CLASS__ . ': configured serializer "igbinary" not available' );
+                       }
                        $this->serializer = Redis::SERIALIZER_IGBINARY;
                } elseif ( $options['serializer'] === 'none' ) {
                        $this->serializer = Redis::SERIALIZER_NONE;
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 fea1bf6..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 ) {
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 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 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 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 2737e35..b83be91 100644 (file)
@@ -69,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,
 
@@ -119,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,
@@ -144,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,
@@ -160,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,
@@ -168,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,
@@ -192,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,
@@ -204,7 +209,12 @@ class SpecialPageFactory {
                'NewSection' => \SpecialNewSection::class,
                'PermanentLink' => \SpecialPermanentLink::class,
                'Redirect' => \SpecialRedirect::class,
-               'Revisiondelete' => \SpecialRevisionDelete::class,
+               'Revisiondelete' => [
+                       'class' => \SpecialRevisionDelete::class,
+                       'services' => [
+                               'PermissionManager',
+                       ],
+               ],
                'RunJobs' => \SpecialRunJobs::class,
                'Specialpages' => \SpecialSpecialpages::class,
                'PageData' => \SpecialPageData::class,
@@ -282,8 +292,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' ) ) {
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 3c009c3..527b910 100644 (file)
@@ -39,6 +39,11 @@ class SpecialPagesWithProp extends QueryPage {
         */
        private $existingPropNames = null;
 
+       /**
+        * @var string|null
+        */
+       private $ns;
+
        /**
         * @var bool
         */
@@ -78,6 +83,13 @@ class SpecialPagesWithProp extends QueryPage {
                                'label-message' => 'pageswithprop-prop',
                                'required' => true,
                        ],
+                       'namespace' => [
+                               'type' => 'namespaceselect',
+                               'name' => 'namespace',
+                               'label-message' => 'namespace',
+                               'all' => null,
+                               'default' => null,
+                       ],
                        'reverse' => [
                                'type' => 'check',
                                'name' => 'reverse',
@@ -108,6 +120,7 @@ class SpecialPagesWithProp extends QueryPage {
 
        public function onSubmit( $data, $form ) {
                $this->propName = $data['propname'];
+               $this->ns = $data['namespace'];
                parent::execute( $data['propname'] );
        }
 
@@ -134,7 +147,7 @@ class SpecialPagesWithProp extends QueryPage {
        }
 
        public function getQueryInfo() {
-               return [
+               $query = [
                        'tables' => [ 'page_props', 'page' ],
                        'fields' => [
                                'page_id' => 'pp_page',
@@ -153,6 +166,12 @@ class SpecialPagesWithProp extends QueryPage {
                        ],
                        'options' => []
                ];
+
+               if ( $this->ns && isset( $this->ns ) ) {
+                       $query['conds']['page_namespace'] = $this->ns;
+               }
+
+               return $query;
        }
 
        function getOrderFields() {
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
+               ] );
        }
 
        /**
index 7444225..437263f 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Permissions\PermissionManager;
 
 /**
  * Special page allowing users with the appropriate permissions to view
@@ -66,6 +67,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
        /** @var string */
        private $otherReason;
 
+       /** @var PermissionManager */
+       private $permissionManager;
+
        /**
         * UI labels for each type.
         */
@@ -107,8 +111,15 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
                ],
        ];
 
-       public function __construct() {
+       /**
+        * @inheritDoc
+        *
+        * @param PermissionManager $permissionManager
+        */
+       public function __construct( PermissionManager $permissionManager ) {
                parent::__construct( 'Revisiondelete', 'deleterevision' );
+
+               $this->permissionManager = $permissionManager;
        }
 
        public function doesWrites() {
@@ -124,13 +135,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
                $output = $this->getOutput();
                $user = $this->getUser();
 
-               // Check blocks
-               // @TODO Use PermissionManager::isBlockedFrom() instead.
-               $block = $user->getBlock();
-               if ( $block ) {
-                       throw new UserBlockedError( $block );
-               }
-
                $this->setHeaders();
                $this->outputHeader();
                $request = $this->getRequest();
@@ -180,6 +184,11 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
                        return;
                }
 
+               // Check blocks
+               if ( $this->permissionManager->isBlockedFrom( $user, $this->targetObj ) ) {
+                       throw new UserBlockedError( $user->getBlock() );
+               }
+
                $this->typeLabels = self::$UILabels[$this->typeName];
                $list = $this->getList();
                $list->reset();
index 375694b..530c580 100644 (file)
@@ -52,7 +52,8 @@ class SpecialRunJobs extends UnlistedSpecialPage {
                }
 
                // Validate request parameters
-               $optional = [ 'maxjobs' => 0, 'maxtime' => 30, 'type' => false, 'async' => true ];
+               $optional = [ 'maxjobs' => 0, 'maxtime' => 30, 'type' => false,
+                       'async' => true, 'stats' => false ];
                $required = array_flip( [ 'title', 'tasks', 'signature', 'sigexpiry' ] );
                $params = array_intersect_key( $this->getRequest()->getValues(), $required + $optional );
                $missing = array_diff_key( $required, $params );
@@ -95,14 +96,20 @@ class SpecialRunJobs extends UnlistedSpecialPage {
                                DeferredUpdates::POSTSEND
                        );
                } else {
-                       $this->doRun( $params );
-                       print "Done\n";
+                       $stats = $this->doRun( $params );
+
+                       if ( $params['stats'] ) {
+                               $this->getRequest()->response()->header( 'Content-Type: application/json' );
+                               print FormatJson::encode( $stats );
+                       } else {
+                               print "Done\n";
+                       }
                }
        }
 
        protected function doRun( array $params ) {
                $runner = new JobRunner( LoggerFactory::getInstance( 'runJobs' ) );
-               $runner->run( [
+               return $runner->run( [
                        'type'     => $params['type'],
                        'maxJobs'  => $params['maxjobs'] ?: 1,
                        'maxTime'  => $params['maxtime'] ?: 30
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';
-       }
-}
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 8bf73c2..d71750b 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 !== '' ) {
@@ -1798,7 +1763,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' );
        }
 
        /**
@@ -2226,7 +2191,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;
        }
@@ -2236,7 +2201,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;
                }
@@ -2302,62 +2269,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' );
                }
 
@@ -3791,7 +3739,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 ) );
@@ -4001,8 +3949,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,
@@ -4032,14 +3978,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;
@@ -4238,16 +4182,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();
        }
 
        /**
@@ -5318,10 +5258,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',
@@ -5334,21 +5272,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 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 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 6ba40ff..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:",
index f6ea46c..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",
        "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",
        "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 f8709f3..64725b4 100644 (file)
@@ -6,7 +6,8 @@
                        "Matma Rex",
                        "Sp5uhe",
                        "Stlmch",
-                       "Railfail536"
+                       "Railfail536",
+                       "Rail"
                ]
        },
        "exif-imagewidth": "Szerokość",
index 5132718..41b2293 100644 (file)
        "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 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.",
+       "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é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.",
        "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 !",
        "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-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é.",
        "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.",
index 9faad52..2a427a2 100644 (file)
        "category-file-count": "{{PLURAL:$2|ह्या वर्गांत फकत सकयली फायल आसपावता.|ह्या वर्गांत सकयल दिल्लीं {{PLURAL:$1|फायल|$1 फायलीं}} आसता, वट्ट फायलीं $2}}",
        "listingcontinuesabbrev": "चालू.",
        "noindex-category": "बिननिर्देशांकी पानां",
+       "broken-file-category": "तुटलेल्या फायलींचो दुवे आसलेलीं पानां",
        "about": "विशीं",
        "article": "मजकूराचीं पानां",
        "newwindow": "(नव्या ज़ोणेलांत उकतें जाता)",
        "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": "वापरप्याचे नांव घालात",
        "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": "लिपयात",
        "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": "खंयचेंय पान",
        "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-contributions": "{{GENDER:$1|ह्या वापरप्याची}} योगदानाची वळेरी",
        "tooltip-t-emailuser": "{{GENDER:$1|ह्या उपेगकर्त्याक}} इ-मेल धाडात",
        "tooltip-t-upload": "फायली अपलोड करात",
        "tooltip-t-specialpages": "सगळ्या विशेश पानांची वळेरी",
        "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": "← आदलें संपादन",
index 1fe99ac..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)",
        "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",
        "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}}",
        "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",
        "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...",
        "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 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 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 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 25d7bf1..04fec53 100644 (file)
                        "Railfail536",
                        "Vlad5250",
                        "CiaPan",
-                       "BadDog"
+                       "BadDog",
+                       "Rail"
                ]
        },
        "tog-underline": "Podkreślenie linków:",
index 478a52e..a33608f 100644 (file)
        "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}}",
        "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}}",
        "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 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 cd35ab2..8446ebd 100644 (file)
        "morenotlisted": "Ńy je to kůmplytno lista",
        "mypage": "Zajta",
        "mytalk": "Dyskusyjŏ",
-       "anontalk": "Godka tygo IP",
+       "anontalk": "Dyskusyjŏ",
        "navigation": "Nawigacyjŏ",
        "and": "&#32;i",
        "faq": "FAQ",
        "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)",
        "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.",
        "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": "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.",
        "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.",
        "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",
        "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",
        "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ć.",
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 208449b..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": "變更頁面的內容模型",
index 68329aa..f89fa62 100644 (file)
@@ -20,7 +20,7 @@
  * @defgroup Maintenance Maintenance
  */
 
-define( MW_ENTRY_POINT, 'cli' );
+define( 'MW_ENTRY_POINT', 'cli' );
 
 // Bail on old versions of PHP, or if composer has not been run yet to install
 // dependencies.
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 4d0c0e6..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' );
@@ -116,7 +119,7 @@ class CleanupImages extends TableCleanup {
         * @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 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 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 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.
         *
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 4a50cc5..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
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 05b78d4..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();
index 39b2ff0..3a43ae8 100644 (file)
@@ -150,6 +150,13 @@ 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
         */
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 3de0070..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();
                }
 
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();
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 0d506ef..9f20e67 100644 (file)
@@ -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 b3aee84..8077022 100644 (file)
@@ -1028,7 +1028,6 @@ return [
        ],
        'mediawiki.RegExp' => [
                'deprecated' => 'Please use mw.util.escapeRegExp() instead.',
-               'scripts' => 'resources/src/mediawiki.RegExp.js',
                'targets' => [ 'desktop', 'mobile' ],
                'dependencies' => [
                        'mediawiki.util',
@@ -1702,7 +1701,6 @@ return [
                        'mediawiki.util',
                        'mediawiki.Title',
                        'mediawiki.jqueryMsg',
-                       'mediawiki.util',
                ],
                'messages' => [
                        'watch',
@@ -2315,7 +2313,6 @@ return [
                'dependencies' => [
                        'mediawiki.api',
                        'mediawiki.jqueryMsg',
-                       'jquery.throttle-debounce',
                        'mediawiki.htmlform.checker',
                ],
        ],
diff --git a/resources/src/mediawiki.RegExp.js b/resources/src/mediawiki.RegExp.js
deleted file mode 100644 (file)
index 258bc2c..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-( function () {
-       mw.RegExp = {};
-       // Backwards-compatible alias; @deprecated since 1.34
-       mw.log.deprecate( mw.RegExp, 'escape', mw.util.escapeRegExp, 'Use mw.util.escapeRegExp() instead.', 'mw.RegExp.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 6342011..57843cb 100644 (file)
@@ -13,8 +13,7 @@ require( './jquery.accessKeyLabel.js' );
  * @return {string} Encoded string
  */
 function rawurlencode( str ) {
-       str = String( str );
-       return encodeURIComponent( str )
+       return encodeURIComponent( String( str ) )
                .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' )
                .replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /~/g, '%7E' );
 }
@@ -59,33 +58,29 @@ util = {
        rawurlencode: rawurlencode,
 
        /**
-        * Encode string into HTML id compatible form suitable for use in HTML
-        * Analog to PHP Sanitizer::escapeIdForAttribute()
+        * Encode a string as CSS id, for use as HTML id attribute value.
         *
-        * @since 1.30
+        * Analog to `Sanitizer::escapeIdForAttribute()` in PHP.
         *
+        * @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 );
+               return escapeIdInternal( str, config.FragmentMode[ 0 ] );
        },
 
        /**
-        * Encode string into HTML id compatible form suitable for use in links
-        * Analog to PHP Sanitizer::escapeIdForLink()
+        * Encode a string as URL fragment, for use as HTML anchor link.
         *
-        * @since 1.30
+        * Analog to `Sanitizer::escapeIdForLink()` in PHP.
         *
+        * @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 );
+               return escapeIdInternal( str, config.FragmentMode[ 0 ] );
        },
 
        /**
@@ -126,16 +121,15 @@ util = {
         * @return {string} Url of the page with name of `pageName`
         */
        getUrl: function ( pageName, params ) {
-               var titleFragmentStart, url, query,
-                       fragment = '',
+               var fragmentIdx, 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 );
+               fragmentIdx = title.indexOf( '#' );
+               if ( fragmentIdx !== -1 ) {
+                       fragment = title.slice( fragmentIdx + 1 );
                        // Exclude the fragment from the page name
-                       title = title.slice( 0, titleFragmentStart );
+                       title = title.slice( 0, fragmentIdx );
                }
 
                // Produce query string
@@ -152,7 +146,7 @@ util = {
                }
 
                // Append the encoded fragment
-               if ( fragment.length ) {
+               if ( fragment && fragment.length ) {
                        url += '#' + util.escapeIdForLink( fragment );
                }
 
@@ -160,16 +154,14 @@ util = {
        },
 
        /**
-        * Get address to a script in the wiki root.
-        * For index.php use `mw.config.get( 'wgScript' )`.
+        * Get URL to a MediaWiki entry point.
         *
         * @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' )
+        * @param {string} [str="index"] Name of MW entry point (e.g. 'index' or 'api')
+        * @return {string} URL to the script file (e.g. '/w/api.php' )
         */
        wikiScript: function ( str ) {
-               str = str || 'index';
-               if ( str === 'index' ) {
+               if ( !str || str === 'index' ) {
                        return mw.config.get( 'wgScript' );
                } else if ( str === 'load' ) {
                        return config.LoadScript;
@@ -195,7 +187,7 @@ util = {
         */
        addCSS: function ( text ) {
                var s = mw.loader.addStyleTag( text );
-               return s.sheet || s.styleSheet || s;
+               return s.sheet;
        },
 
        /**
@@ -440,15 +432,15 @@ util = {
         * @return {boolean}
         */
        isIPv4Address: function ( address, allowBlock ) {
-               var block, RE_IP_BYTE, RE_IP_ADD;
+               var block,
+                       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;
 
                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 ) );
        },
@@ -533,6 +525,11 @@ util = {
        }
 };
 
+// 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 ) {
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 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 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..7dc63fb 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 
+use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\Block\Restriction\PageRestriction;
+
 /**
  * Tests for action=revisiondelete
  * @covers APIRevisionDelete
@@ -22,8 +25,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 );
                }
        }
 
@@ -114,4 +116,32 @@ class ApiRevisionDeleteTest extends ApiTestCase {
                $this->assertTrue( $item['texthidden'], 'texthidden' );
                $this->assertEquals( $item['id'], $revid );
        }
+
+       public function testPartiallyBlockedPage() {
+               $this->setExpectedApiException( 'apierror-blocked-partial' );
+
+               $user = static::getTestSysop()->getUser();
+
+               $block = new DatabaseBlock( [
+                       'address' => $user,
+                       'by' => static::getTestSysop()->getUser()->getId(),
+                       'sitewide' => false,
+               ] );
+
+               $block->setRestrictions( [
+                       new PageRestriction( 0, Title::newFromText( self::$page )->getArticleID() )
+               ] );
+               $block->insert();
+
+               $revid = array_shift( $this->revs );
+
+               $this->doApiRequest( [
+                       'action' => 'revisiondelete',
+                       'type' => 'revision',
+                       'target' => self::$page,
+                       'ids' => $revid,
+                       'hide' => 'content|user|comment',
+                       'token' => $user->getEditToken(),
+               ] );
+       }
 }
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 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 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 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 5d4606d..2a4ba4b 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
         */
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(