Merge "Provide command to adjust phpunit.xml for code coverage"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 9 Sep 2019 19:25:32 +0000 (19:25 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 9 Sep 2019 19:25:32 +0000 (19:25 +0000)
290 files changed:
RELEASE-NOTES-1.34
api.php
autoload.php
docs/extension.schema.v1.json
docs/extension.schema.v2.json
docs/hooks.txt
img_auth.php
includes/ActorMigration.php
includes/AjaxResponse.php
includes/DefaultSettings.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/Message/TextFormatter.php
includes/OutputPage.php
includes/PHPVersionCheck.php
includes/PathRouter.php
includes/Rest/EntryPoint.php
includes/Rest/Handler.php
includes/Rest/Handler/HelloHandler.php
includes/Rest/HttpException.php
includes/Rest/ResponseFactory.php
includes/Rest/Router.php
includes/Rest/SimpleHandler.php
includes/Rest/Validator/BodyValidator.php [new file with mode: 0644]
includes/Rest/Validator/NullBodyValidator.php [new file with mode: 0644]
includes/Rest/Validator/ParamValidatorCallbacks.php [new file with mode: 0644]
includes/Rest/Validator/Validator.php [new file with mode: 0644]
includes/Revision.php
includes/ServiceWiring.php
includes/Setup.php
includes/Title.php
includes/WebRequest.php
includes/actions/InfoAction.php
includes/api/ApiBase.php
includes/api/ApiExpandTemplates.php
includes/api/ApiMain.php
includes/api/ApiQueryAllRevisions.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryUserContribs.php
includes/api/ApiQueryUserInfo.php
includes/api/i18n/fr.json
includes/api/i18n/ru.json
includes/auth/AuthenticationRequest.php
includes/auth/ResetPasswordSecondaryAuthenticationProvider.php
includes/block/DatabaseBlock.php
includes/cache/HTMLFileCache.php
includes/cache/UserCache.php
includes/changes/RecentChange.php
includes/config/ConfigFactory.php
includes/config/ConfigRepository.php
includes/context/RequestContext.php
includes/dao/DBAccessBase.php
includes/debug/MWDebug.php
includes/debug/logger/ConsoleLogger.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/JobQueueEnqueueUpdate.php
includes/deferred/MessageCacheUpdate.php
includes/deferred/SiteStatsUpdate.php
includes/deferred/UserEditCountUpdate.php
includes/diff/DifferenceEngine.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/LocalFileDeleteBatch.php
includes/filerepo/file/OldLocalFile.php
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLCheckMatrix.php
includes/import/ImportStreamSource.php
includes/import/ImportStringSource.php
includes/installer/DatabaseUpdater.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteInstaller.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/ar.json
includes/installer/i18n/ckb.json
includes/installer/i18n/fr.json
includes/installer/i18n/it.json
includes/installer/i18n/nl.json
includes/installer/i18n/qqq.json
includes/installer/i18n/sh.json
includes/libs/Message/ITextFormatter.php
includes/libs/Message/ListParam.php
includes/libs/Message/ListType.php
includes/libs/Message/MessageParam.php
includes/libs/Message/MessageValue.php
includes/libs/Message/ParamType.php
includes/libs/Message/README.md [new file with mode: 0644]
includes/libs/Message/ScalarParam.php [new file with mode: 0644]
includes/libs/Message/TextParam.php [deleted file]
includes/libs/StatusValue.php
includes/libs/filebackend/FSFileBackend.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/http/MultiHttpClient.php
includes/libs/objectcache/wancache/WANObjectCache.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/lbfactory/LBFactoryMulti.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php
includes/libs/rdbms/lbfactory/LBFactorySingle.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/logging/LogPage.php
includes/logging/ManualLogEntry.php
includes/page/Article.php
includes/page/ImagePage.php
includes/page/WikiPage.php
includes/pager/IndexPager.php
includes/parser/PPDPart.php
includes/parser/PPDStack.php
includes/parser/PPDStackElement.php
includes/parser/PPFrame.php
includes/parser/PPFrame_DOM.php
includes/parser/PPTemplateFrame_Hash.php
includes/parser/Preprocessor.php
includes/parser/Preprocessor_Hash.php
includes/registration/ExtensionRegistry.php
includes/resourceloader/ResourceLoaderFileModule.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevisionDeleteUser.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialContributions.php
includes/specials/SpecialLog.php
includes/specials/SpecialPageData.php
includes/specials/SpecialRecentChangesLinked.php
includes/specials/SpecialStatistics.php
includes/specials/pagers/ActiveUsersPager.php
includes/specials/pagers/NewFilesPager.php
includes/upload/UploadFromStash.php
includes/user/User.php
includes/watcheditem/NoWriteWatchedItemStore.php
index.php
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/ban.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ckb.json
languages/i18n/co.json
languages/i18n/diq.json
languages/i18n/dtp.json
languages/i18n/en.json
languages/i18n/eu.json
languages/i18n/exif/diq.json
languages/i18n/exif/fr.json
languages/i18n/exif/ia.json
languages/i18n/exif/id.json
languages/i18n/exif/mk.json
languages/i18n/exif/nds-nl.json
languages/i18n/exif/sd.json
languages/i18n/exif/szl.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fit.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gom-deva.json
languages/i18n/gom-latn.json
languages/i18n/he.json
languages/i18n/hu.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/khw.json
languages/i18n/ko.json
languages/i18n/luz.json
languages/i18n/min.json
languages/i18n/mni.json
languages/i18n/my.json
languages/i18n/nap.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nqo.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/sk.json
languages/i18n/szl.json
languages/i18n/te.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesHyw.php
load.php
maintenance/Doxyfile
maintenance/Maintenance.php
maintenance/archives/patch-drop-user-fields.sql [new file with mode: 0644]
maintenance/cleanupImages.php
maintenance/compareParsers.php
maintenance/convertLinks.php
maintenance/dumpIterator.php
maintenance/dumpUploads.php
maintenance/getReplicaServer.php
maintenance/importDump.php
maintenance/importImages.php
maintenance/includes/BackupDumper.php
maintenance/includes/MWDoxygenFilter.php [new file with mode: 0644]
maintenance/includes/MigrateActors.php
maintenance/jsduck/categories.json
maintenance/mergeMessageFileList.php
maintenance/mwdoc-filter.php
maintenance/mwdocgen.php
maintenance/postgres/tables.sql
maintenance/preprocessDump.php
maintenance/preprocessorFuzzTest.php
maintenance/reassignEdits.php
maintenance/rebuildFileCache.php
maintenance/rebuildImages.php
maintenance/recountCategories.php
maintenance/removeUnusedAccounts.php
maintenance/renderDump.php
maintenance/resetUserTokens.php
maintenance/sqlite/archives/patch-archive-drop-ar_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-filearchive-drop-fa_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-image-drop-img_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-ipblocks-drop-ipb_by.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-logging-drop-log_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-oldimage-drop-oi_user.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-recentchanges-drop-rc_user.sql [new file with mode: 0644]
maintenance/storage/recompressTracked.php
maintenance/tables.sql
opensearch_desc.php
phpunit.xml.dist
profileinfo.php
resources/Resources.php
resources/src/jquery.tablesorter/jquery.tablesorter.js
resources/src/jquery/jquery.accessKeyLabel.js [deleted file]
resources/src/jquery/jquery.highlightText.js
resources/src/mediawiki.RegExp.js [deleted file]
resources/src/mediawiki.Title/Title.js
resources/src/mediawiki.htmlform.checker.js
resources/src/mediawiki.htmlform/cloner.js
resources/src/mediawiki.inspect.js
resources/src/mediawiki.page.watch.ajax.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.monobook.less [deleted file]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.vector.less [deleted file]
resources/src/mediawiki.rcfilters/ui/MainWrapperWidget.js
resources/src/mediawiki.util.js [deleted file]
resources/src/mediawiki.util/.eslintrc.json [new file with mode: 0644]
resources/src/mediawiki.util/jquery.accessKeyLabel.js [new file with mode: 0644]
resources/src/mediawiki.util/util.js [new file with mode: 0644]
resources/src/mediawiki.widgets.datetime/DateTimeFormatter.js
resources/src/moment/moment-locale-overrides.js
rest.php
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiIntegrationTestCase.php
tests/phpunit/MediaWikiUnitTestCase.php
tests/phpunit/includes/ActorMigrationTest.php
tests/phpunit/includes/ActorMigrationTest.sql [new file with mode: 0644]
tests/phpunit/includes/Message/TextFormatterTest.php
tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php
tests/phpunit/includes/Rest/EntryPointTest.php
tests/phpunit/includes/Revision/RevisionQueryInfoTest.php
tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/filebackend/FileBackendTest.php
tests/phpunit/includes/libs/Message/MessageValueTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
tests/phpunit/includes/logging/DatabaseLogEntryTest.php
tests/phpunit/includes/page/PageArchiveTestBase.php
tests/phpunit/includes/registration/ExtensionRegistryTest.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/maintenance/MWDoxygenFilterTest.php [new file with mode: 0644]
tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php
tests/phpunit/unit/includes/Rest/RouterTest.php
tests/phpunit/unit/includes/installer/SqliteInstallerTest.php [new file with mode: 0644]
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js [deleted file]
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
thumb.php
thumb_handler.php

index 5cd1bbc..9ac26e8 100644 (file)
@@ -26,6 +26,13 @@ For notes on 1.33.x and older releases, see HISTORY.
 
 === Configuration changes for system administrators in 1.34 ===
 
+In an effort to enforce best practices for passwords, MediaWiki will now warn
+users, and suggest that they change their password, if it is in the list of
+100,000 commonly used passwords that are considered bad passwords. If you want
+to disable this for your users, please add the following to your local settings:
+
+$wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
+
 ==== New configuration ====
 * $wgAllowExternalReqID (T201409) - This configuration setting controls whether
   Mediawiki accepts the request ID set by the incoming request via the
@@ -66,12 +73,14 @@ For notes on 1.33.x and older releases, see HISTORY.
   which was deprecated in 1.30, no longer works. Instead, $wgProxyList should be
   an array with IP addresses as the values, or a string path to a file
   containing one IP address per line.
+* $wgCookieSetOnAutoblock and $wgCookieSetOnIpBlock are now enabled by default.
 * …
 
 ==== Removed configuration ====
 * $wgWikiDiff2MovedParagraphDetectionCutoff — If you still want a custom change
   size threshold, please specify in php.ini, using the configuration variable
   wikidiff2.moved_paragraph_detection_cutoff.
+* $wgUseESI - This experimental setting, deprecated in 1.33, is now removed.
 * $wgDebugPrintHttpHeaders - The default of including HTTP headers in the
   debug log channel is no longer configurable. The debug log itself remains
   configurable via $wgDebugLogFile.
@@ -82,6 +91,8 @@ For notes on 1.33.x and older releases, see HISTORY.
 * $wgDBOracleDRCP - If you must use persistent connections, set DBO_PERSISTENT
   in the 'flags' field for servers in $wgDBServers (or $wgLBFactoryConf).
 * $wgMemCachedDebug - Set the cache "debug" field in $wgObjectCaches instead.
+* $wgActorTableSchemaMigrationStage has been removed. Extension code for
+  MediaWiki 1.31+ finding it unset should treat it as being SCHEMA_COMPAT_NEW.
 
 === New user-facing features in 1.34 ===
 * Special:Mute has been added as a quick way for users to block unwanted emails
@@ -109,6 +120,9 @@ For notes on 1.33.x and older releases, see HISTORY.
   GetBlockedStatus.
 * ObjectFactory is available as a service. When used as a service, the object
   specs can now specify needed DI services.
+* (T222388) Special pages can now be specified as an ObjectFactory spec,
+  allowing the construction of special pages that require services to be
+  injected in their constructor.
 
 === External library changes in 1.34 ===
 
@@ -361,7 +375,21 @@ because of Phabricator reports.
   initialized after calling SearchResult::initFromTitle().
 * The UserIsBlockedFrom hook is only called if a block is found first, and
   should only be used to unblock a blocked user.
-* …
+* Parameters for index.php from PATH_INFO, such as the title, are no longer
+  written to $_GET.
+* The selectFields() methods on classes LocalFile, ArchivedFile, OldLocalFile,
+  DatabaseBlock, and RecentChange, deprecated in 1.31, have been removed. Use
+  the corresponding getQueryInfo() methods instead.
+* The following methods on Revision, deprecated since 1.31, have been removed.
+  Use RevisionStore::getQueryInfo() or RevisionStore::getArchiveQueryInfo()
+  instead.
+  * Revision::userJoinCond()
+  * Revision::pageJoinCond()
+  * Revision::selectFields()
+  * Revision::selectArchiveFields()
+  * Revision::selectTextFields()
+  * Revision::selectPageFields()
+  * Revision::selectUserFields()
 
 === Deprecations in 1.34 ===
 * The MWNamespace class is deprecated. Use NamespaceInfo.
@@ -417,6 +445,8 @@ because of Phabricator reports.
 * ResourceLoaderContext::getConfig and ResourceLoaderContext::getLogger have
   been deprecated. Inside ResourceLoaderModule subclasses, use the local methods
   instead. Elsewhere, use the methods from the ResourceLoader class.
+* The 'jquery.accessKeyLabel' module has been deprecated. This jQuery
+  plugin is now ships as part of the 'mediawiki.util' module bundle.
 * The Profiler::setTemplated and Profiler::getTemplated methods have been
   deprecated. Use Profiler::setAllowOutput and Profiler::getAllowOutput
   instead.
@@ -479,6 +509,14 @@ because of Phabricator reports.
   class. If you extend this class please be sure to override all its methods
   or extend RevisionSearchResult.
 * Skin::getSkinNameMessages() is deprecated and no longer used.
+* The mediawiki.RegExp module is deprecated; use mw.util.escapeRegExp() instead.
+* Specifying a SpecialPage object for the list of special pages (either through
+  the SpecialPage_initList hook or by adding to $wgSpecialPages) is now
+  deprecated.
+* Use of ActorMigration with 'ar_user', 'img_user', 'oi_user', 'fa_user',
+  'rc_user', 'log_user', and 'ipb_by' is deprecated. Queries should be adjusted
+  to use the corresponding actor fields directly. Note that use with
+  'rev_user' is *not* deprecated at this time.
 
 === Other changes in 1.34 ===
 * …
diff --git a/api.php b/api.php
index 0fb674b..6f4bac3 100644 (file)
--- a/api.php
+++ b/api.php
@@ -31,6 +31,7 @@ use MediaWiki\Logger\LegacyLogger;
 
 // So extensions (and other code) can check whether they're running in API mode
 define( 'MW_API', true );
+define( 'MW_ENTRY_POINT', 'api' );
 
 require __DIR__ . '/includes/WebStart.php';
 
@@ -44,7 +45,7 @@ if ( !$wgRequest->checkUrlExtension() ) {
 // PATH_INFO can be used for stupid things. We don't support it for api.php at
 // all, so error out if it's present.
 if ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) {
-       $correctUrl = wfAppendQuery( wfScript( 'api' ), $wgRequest->getQueryValues() );
+       $correctUrl = wfAppendQuery( wfScript( 'api' ), $wgRequest->getQueryValuesOnly() );
        $correctUrl = wfExpandUrl( $correctUrl, PROTO_CANONICAL );
        header( "Location: $correctUrl", true, 301 );
        echo 'This endpoint does not support "path info", i.e. extra text between "api.php"'
index 413d315..87b2bb9 100644 (file)
@@ -830,6 +830,7 @@ $wgAutoloadLocalClasses = [
        'MWCryptRand' => __DIR__ . '/includes/utils/MWCryptRand.php',
        'MWDebug' => __DIR__ . '/includes/debug/MWDebug.php',
        'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
+       'MWDoxygenFilter' => __DIR__ . '/maintenance/includes/MWDoxygenFilter.php',
        'MWException' => __DIR__ . '/includes/exception/MWException.php',
        'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
        'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
index 9ce016f..06701cd 100644 (file)
                },
                "SpecialPages": {
                        "type": "object",
-                       "description": "SpecialPages implemented in this extension (mapping of page name to class name)"
+                       "description": "SpecialPages implemented in this extension (mapping of page name to class name or to ObjectFactory spec)"
                },
                "AutoloadNamespaces": {
                        "type": "object",
index 9d874f4..56d274b 100644 (file)
                },
                "SpecialPages": {
                        "type": "object",
-                       "description": "SpecialPages implemented in this extension (mapping of page name to class name)"
+                       "description": "SpecialPages implemented in this extension (mapping of page name to class name or to ObjectFactory spec)"
                },
                "AutoloadNamespaces": {
                        "type": "object",
index b7ea02c..a248c29 100644 (file)
@@ -2827,6 +2827,7 @@ or request state must be added through MakeGlobalVariablesScript instead.
 Skin is made available for skin specific config.
 &$vars: [ variable name => value ]
 $skin: Skin
+$config: Config object (since 1.34)
 
 'ResourceLoaderJqueryMsgModuleMagicWords': Called in
 ResourceLoaderJqueryMsgModule to allow adding magic words for jQueryMsg.
index 6e45e4e..f23de4f 100644 (file)
@@ -39,6 +39,7 @@
  */
 
 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
+define( 'MW_ENTRY_POINT', 'img_auth' );
 require __DIR__ . '/includes/WebStart.php';
 
 # Set action base paths so that WebRequest::getPathInfo()
index 5dde8a0..c79074d 100644 (file)
@@ -28,15 +28,18 @@ use Wikimedia\Rdbms\IDatabase;
  * This class handles the logic for the actor table migration.
  *
  * This is not intended to be a long-term part of MediaWiki; it will be
- * deprecated and removed along with $wgActorTableSchemaMigrationStage.
+ * deprecated and removed once actor migration is complete.
  *
  * @since 1.31
+ * @since 1.34 Use with 'ar_user', 'img_user', 'oi_user', 'fa_user',
+ *  'rc_user', 'log_user', and 'ipb_by' is deprecated. Callers should
+ *  reference the corresponding actor fields directly.
  */
 class ActorMigration {
 
        /**
         * Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage
-        * expects MIGRATION_* or SCHEMA_COMPAT_*
+        * (in MW <1.34) expects MIGRATION_* or SCHEMA_COMPAT_*
         */
        const MIGRATION_STAGE_SCHEMA_COMPAT = 1;
 
@@ -68,6 +71,28 @@ class ActorMigration {
         */
        private static $formerTempTables = [];
 
+       /**
+        * Define fields that are deprecated for use with this class.
+        * @var (string|null)[] Keys are '$key', value is null for soft deprecation
+        *  or a string naming the deprecated version for hard deprecation.
+        */
+       private static $deprecated = [
+               'ar_user' => null, // 1.34
+               'img_user' => null, // 1.34
+               'oi_user' => null, // 1.34
+               'fa_user' => null, // 1.34
+               'rc_user' => null, // 1.34
+               'log_user' => null, // 1.34
+               'ipb_by' => null, // 1.34
+       ];
+
+       /**
+        * Define fields that are removed for use with this class.
+        * @var string[] Keys are '$key', value is the MediaWiki version in which
+        *  use was removed.
+        */
+       private static $removed = [];
+
        /**
         * Define fields that use non-standard mapping
         * @var array Keys are the user id column name, values are arrays with two
@@ -112,6 +137,21 @@ class ActorMigration {
                return MediaWikiServices::getInstance()->getActorMigration();
        }
 
+       /**
+        * Issue deprecation warning/error as appropriate.
+        * @param string $key
+        */
+       private static function checkDeprecation( $key ) {
+               if ( isset( self::$removed[$key] ) ) {
+                       throw new InvalidArgumentException(
+                               "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
+                       );
+               }
+               if ( !empty( self::$deprecated[$key] ) ) {
+                       wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
+               }
+       }
+
        /**
         * Return an SQL condition to test if a user field is anonymous
         * @param string $field Field name or SQL fragment
@@ -152,6 +192,8 @@ class ActorMigration {
         * @phan-return array{tables:string[],fields:string[],joins:array}
         */
        public function getJoin( $key ) {
+               self::checkDeprecation( $key );
+
                if ( !isset( $this->joinCache[$key] ) ) {
                        $tables = [];
                        $fields = [];
@@ -203,6 +245,8 @@ class ActorMigration {
         * @return array to merge into `$values` to `IDatabase->update()` or `$a` to `IDatabase->insert()`
         */
        public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
+               self::checkDeprecation( $key );
+
                if ( isset( self::$tempTables[$key] ) ) {
                        throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
                }
@@ -236,6 +280,8 @@ class ActorMigration {
         *    and extra fields needed for the temp table.
         */
        public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
+               self::checkDeprecation( $key );
+
                if ( isset( self::$formerTempTables[$key] ) ) {
                        wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
                } elseif ( !isset( self::$tempTables[$key] ) ) {
@@ -319,6 +365,8 @@ class ActorMigration {
         *  All tables and joins are aliased, so `+` is safe to use.
         */
        public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
+               self::checkDeprecation( $key );
+
                $tables = [];
                $conds = [];
                $joins = [];
index 323c5d3..0664652 100644 (file)
@@ -182,16 +182,7 @@ class AjaxResponse {
                        if ( $this->mConfig->get( 'UseCdn' ) ) {
                                # Expect explicit purge of the proxy cache, but require end user agents
                                # to revalidate against the proxy on each visit.
-                               # Surrogate-Control controls our CDN, Cache-Control downstream caches
-
-                               if ( $this->mConfig->get( 'UseESI' ) ) {
-                                       wfDeprecated( '$wgUseESI = true', '1.33' );
-                                       header( 'Surrogate-Control: max-age=' . $this->mCacheDuration . ', content="ESI/1.0"' );
-                                       header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
-                               } else {
-                                       header( 'Cache-Control: s-maxage=' . $this->mCacheDuration . ', must-revalidate, max-age=0' );
-                               }
-
+                               header( 'Cache-Control: s-maxage=' . $this->mCacheDuration . ', must-revalidate, max-age=0' );
                        } else {
                                # Let the client do the caching. Cache is not purged.
                                header( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT" );
index 81de1a0..f6ac342 100644 (file)
@@ -2748,12 +2748,6 @@ $wgExtensionInfoMTime = false;
  */
 $wgUseCdn = false;
 
-/**
- * If you run Squid3 with ESI support, enable this (default:false):
- * @deprecated in 1.33. This was a now-defunct experimental feature.
- */
-$wgUseESI = false;
-
 /**
  * Add X-Forwarded-Proto to the Vary and Key headers for API requests and
  * RSS/Atom feeds. Use this if you have an SSL termination setup
@@ -4463,7 +4457,7 @@ $wgCentralIdLookupProvider = 'local';
  *             Deprecated since 1.33. Use PasswordNotInLargeBlacklist instead.
  *     - PasswordNotInLargeBlacklist - Password not in best practices list of
  *             100,000 commonly used passwords. Due to the size of the list this
- *      is a probabilistic test.
+ *             is a probabilistic test.
  *
  * If you add custom checks, for Special:PasswordPolicies to display them correctly,
  * every check should have a corresponding passwordpolicies-policy-<check> message,
@@ -4481,28 +4475,25 @@ $wgPasswordPolicy = [
                'bureaucrat' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'sysop' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'interface-admin' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'bot' => [
                        'MinimalPasswordLength' => 10,
                        'MinimumPasswordLengthToLogin' => 1,
-                       'PasswordNotInLargeBlacklist' => true,
                ],
                'default' => [
                        'MinimalPasswordLength' => [ 'value' => 1, 'suggestChangeOnLogin' => true ],
                        'PasswordCannotMatchUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
                        'PasswordCannotMatchBlacklist' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
                        'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true ],
+                       'PasswordNotInLargeBlacklist' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
                ],
        ],
        'checks' => [
@@ -6071,7 +6062,7 @@ $wgSessionName = false;
  * which case there is a possibility of an attacker discovering the names of revdeleted users, so
  * it is best to use this in conjunction with $wgSecretKey being set).
  */
-$wgCookieSetOnAutoblock = false;
+$wgCookieSetOnAutoblock = true;
 
 /**
  * Whether to set a cookie when a logged-out user is blocked. Doing so means that a blocked user,
@@ -6080,7 +6071,7 @@ $wgCookieSetOnAutoblock = false;
  * case there is a possibility of an attacker discovering the names of revdeleted users, so it
  * is best to use this in conjunction with $wgSecretKey being set).
  */
-$wgCookieSetOnIpBlock = false;
+$wgCookieSetOnIpBlock = true;
 
 /** @} */ # end of cookie settings }
 
@@ -8988,24 +8979,6 @@ $wgMultiContentRevisionSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_
  */
 $wgXmlDumpSchemaVersion = XML_DUMP_SCHEMA_VERSION_10;
 
-/**
- * Actor table schema migration stage.
- *
- * Use the SCHEMA_COMPAT_XXX flags. Supported values:
- * - SCHEMA_COMPAT_OLD
- * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
- * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
- * - SCHEMA_COMPAT_NEW
- *
- * Note that reading the old and new schema at the same time is not supported
- * in 1.32, but was (with significant query performance issues) in 1.31.
- *
- * @since 1.31
- * @since 1.32 changed allowed flags
- * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
- */
-$wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_NEW;
-
 /**
  * Flag to enable Partial Blocks. This allows an admin to prevent a user from editing specific pages
  * or namespaces.
index 6ae4371..c346b75 100644 (file)
@@ -1193,6 +1193,8 @@ class EditPage {
         * @since 1.21
         */
        protected function getContentObject( $def_content = null ) {
+               global $wgDisableAnonTalk;
+
                $content = false;
 
                $user = $this->context->getUser();
@@ -1292,8 +1294,11 @@ class EditPage {
                                                                                $undo
                                                                        )->inContentLanguage()->text();
                                                                } else {
+                                                                       $undoMessage = ( $undorev->getUser() === 0 && $wgDisableAnonTalk ) ?
+                                                                               'undo-summary-anon' :
+                                                                               'undo-summary';
                                                                        $undoSummary = $this->context->msg(
-                                                                               'undo-summary',
+                                                                               $undoMessage,
                                                                                $undo,
                                                                                $userText
                                                                        )->inContentLanguage()->text();
index 1241e1c..e31f9d2 100644 (file)
@@ -36,18 +36,18 @@ class FileDeleteForm {
        private $title = null;
 
        /**
-        * @var File
+        * @var LocalFile
         */
        private $file = null;
 
        /**
-        * @var File
+        * @var LocalFile
         */
        private $oldfile = null;
        private $oldimage = '';
 
        /**
-        * @param File $file File object we're deleting
+        * @param LocalFile $file File object we're deleting
         */
        public function __construct( $file ) {
                $this->title = $file->getTitle();
@@ -451,9 +451,9 @@ class FileDeleteForm {
         * value was provided, does it correspond to an
         * existing, local, old version of this file?
         *
-        * @param File &$file
-        * @param File &$oldfile
-        * @param File $oldimage
+        * @param LocalFile &$file
+        * @param LocalFile &$oldfile
+        * @param LocalFile $oldimage
         * @return bool
         */
        public static function haveDeletableFile( &$file, &$oldfile, $oldimage ) {
index f5eeb16..783dd43 100644 (file)
@@ -45,18 +45,27 @@ class TextFormatter implements ITextFormatter {
                return $this->langCode;
        }
 
-       private static function convertParam( MessageParam $param ) {
+       private function convertParam( MessageParam $param ) {
                if ( $param instanceof ListParam ) {
                        $convertedElements = [];
                        foreach ( $param->getValue() as $element ) {
-                               $convertedElements[] = self::convertParam( $element );
+                               $convertedElements[] = $this->convertParam( $element );
                        }
                        return Message::listParam( $convertedElements, $param->getListType() );
                } elseif ( $param instanceof MessageParam ) {
+                       $value = $param->getValue();
+                       if ( $value instanceof MessageValue ) {
+                               $mv = $value;
+                               $value = $this->createMessage( $mv->getKey() );
+                               foreach ( $mv->getParams() as $mvParam ) {
+                                       $value->params( $this->convertParam( $mvParam ) );
+                               }
+                       }
+
                        if ( $param->getType() === ParamType::TEXT ) {
-                               return $param->getValue();
+                               return $value;
                        } else {
-                               return [ $param->getType() => $param->getValue() ];
+                               return [ $param->getType() => $value ];
                        }
                } else {
                        throw new \InvalidArgumentException( 'Invalid message parameter type' );
@@ -66,7 +75,7 @@ class TextFormatter implements ITextFormatter {
        public function format( MessageValue $mv ) {
                $message = $this->createMessage( $mv->getKey() );
                foreach ( $mv->getParams() as $param ) {
-                       $message->params( self::convertParam( $param ) );
+                       $message->params( $this->convertParam( $param ) );
                }
                $message->inLanguage( $this->langCode );
                return $message->text();
index 15a759b..7f005fb 100644 (file)
@@ -2414,32 +2414,16 @@ class OutputPage extends ContextSource {
                                $this->mCdnMaxage != 0 &&
                                !$this->haveCacheVaryCookies()
                        ) {
-                               if ( $config->get( 'UseESI' ) ) {
-                                       wfDeprecated( '$wgUseESI = true', '1.33' );
-                                       # We'll purge the proxy cache explicitly, but require end user agents
-                                       # to revalidate against the proxy on each visit.
-                                       # Surrogate-Control controls our CDN, Cache-Control downstream caches
-                                       wfDebug( __METHOD__ .
-                                               ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
-                                       # start with a shorter timeout for initial testing
-                                       # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
-                                       $response->header(
-                                               "Surrogate-Control: max-age={$config->get( 'CdnMaxAge' )}" .
-                                               "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
-                                       );
-                                       $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
-                               } else {
-                                       # We'll purge the proxy cache for anons explicitly, but require end user agents
-                                       # to revalidate against the proxy on each visit.
-                                       # IMPORTANT! The CDN needs to replace the Cache-Control header with
-                                       # Cache-Control: s-maxage=0, must-revalidate, max-age=0
-                                       wfDebug( __METHOD__ .
-                                               ": local proxy caching; {$this->mLastModified} **", 'private' );
-                                       # start with a shorter timeout for initial testing
-                                       # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
-                                       $response->header( "Cache-Control: " .
-                                               "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
-                               }
+                               # We'll purge the proxy cache for anons explicitly, but require end user agents
+                               # to revalidate against the proxy on each visit.
+                               # IMPORTANT! The CDN needs to replace the Cache-Control header with
+                               # Cache-Control: s-maxage=0, must-revalidate, max-age=0
+                               wfDebug( __METHOD__ .
+                                       ": local proxy caching; {$this->mLastModified} **", 'private' );
+                               # start with a shorter timeout for initial testing
+                               # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
+                               $response->header( "Cache-Control: " .
+                                       "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
                        } else {
                                # We do want clients to cache if they can, but they *must* check for updates
                                # on revisiting the page.
index b63a84d..bf138c4 100644 (file)
@@ -31,8 +31,6 @@
  *
  * @note This class uses setter methods instead of a constructor so that
  * it can be compatible with PHP 4, PHP 5 and PHP 7 (without warnings).
- *
- * @class
  */
 class PHPVersionCheck {
        /* @var string The number of the MediaWiki version used. */
index 2882e66..4d7bd38 100644 (file)
@@ -401,4 +401,22 @@ class PathRouter {
 
                return $value;
        }
+
+       /**
+        * @internal For use by Title and WebRequest only.
+        * @param array $actionPaths
+        * @param string $articlePath
+        * @return string[]|false
+        */
+       public static function getActionPaths( array $actionPaths, $articlePath ) {
+               if ( !$actionPaths ) {
+                       return false;
+               }
+               // Processing of urls for this feature requires that 'view' is set.
+               // By default, set it to the pretty article path.
+               if ( !isset( $actionPaths['view'] ) ) {
+                       $actionPaths['view'] = $articlePath;
+               }
+               return $actionPaths;
+       }
 }
index f28b4ea..ee3441e 100644 (file)
@@ -6,6 +6,7 @@ use ExtensionRegistry;
 use MediaWiki;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
+use MediaWiki\Rest\Validator\Validator;
 use RequestContext;
 use Title;
 use WebResponse;
@@ -36,6 +37,7 @@ class EntryPoint {
 
                $services = MediaWikiServices::getInstance();
                $conf = $services->getMainConfig();
+               $objectFactory = $services->getObjectFactory();
 
                if ( !$conf->get( 'EnableRestAPI' ) ) {
                        wfHttpError( 403, 'Access Denied',
@@ -51,6 +53,9 @@ class EntryPoint {
                $authorizer = new MWBasicAuthorizer( $context->getUser(),
                        $services->getPermissionManager() );
 
+               // @phan-suppress-next-line PhanAccessMethodInternal
+               $restValidator = new Validator( $objectFactory, $request, RequestContext::getMain()->getUser() );
+
                global $IP;
                $router = new Router(
                        [ "$IP/includes/Rest/coreRoutes.json" ],
@@ -58,7 +63,9 @@ class EntryPoint {
                        $conf->get( 'RestPath' ),
                        $services->getLocalServerObjectCache(),
                        new ResponseFactory,
-                       $authorizer
+                       $authorizer,
+                       $objectFactory,
+                       $restValidator
                );
 
                $entryPoint = new self(
index c05d8e7..efe2b7e 100644 (file)
@@ -2,7 +2,18 @@
 
 namespace MediaWiki\Rest;
 
+use MediaWiki\Rest\Validator\BodyValidator;
+use MediaWiki\Rest\Validator\NullBodyValidator;
+use MediaWiki\Rest\Validator\Validator;
+
 abstract class Handler {
+
+       /**
+        * (string) ParamValidator constant to specify the source of the parameter.
+        * Value must be 'path', 'query', or 'post'.
+        */
+       const PARAM_SOURCE = 'rest-param-source';
+
        /** @var Router */
        private $router;
 
@@ -15,6 +26,12 @@ abstract class Handler {
        /** @var ResponseFactory */
        private $responseFactory;
 
+       /** @var array|null */
+       private $validatedParams;
+
+       /** @var mixed */
+       private $validatedBody;
+
        /**
         * Initialise with dependencies from the Router. This is called after construction.
         * @internal
@@ -68,6 +85,62 @@ abstract class Handler {
                return $this->responseFactory;
        }
 
+       /**
+        * Validate the request parameters/attributes and body. If there is a validation
+        * failure, a response with an error message should be returned or an
+        * HttpException should be thrown.
+        *
+        * @param Validator $restValidator
+        * @throws HttpException On validation failure.
+        */
+       public function validate( Validator $restValidator ) {
+               $validatedParams = $restValidator->validateParams( $this->getParamSettings() );
+               $validatedBody = $restValidator->validateBody( $this->request, $this );
+               $this->validatedParams = $validatedParams;
+               $this->validatedBody = $validatedBody;
+       }
+
+       /**
+        * Fetch ParamValidator settings for parameters
+        *
+        * Every setting must include self::PARAM_SOURCE to specify which part of
+        * the request is to contain the parameter.
+        *
+        * @return array[] Associative array mapping parameter names to
+        *  ParamValidator settings arrays
+        */
+       public function getParamSettings() {
+               return [];
+       }
+
+       /**
+        * Fetch the BodyValidator
+        * @param string $contentType Content type of the request.
+        * @return BodyValidator
+        */
+       public function getBodyValidator( $contentType ) {
+               return new NullBodyValidator();
+       }
+
+       /**
+        * Fetch the validated parameters
+        *
+        * @return array|null Array mapping parameter names to validated values,
+        *  or null if validateParams() was not called yet or validation failed.
+        */
+       public function getValidatedParams() {
+               return $this->validatedParams;
+       }
+
+       /**
+        * Fetch the validated body
+        * @return mixed Value returned by the body validator, or null if validateParams() was
+        *  not called yet, validation failed, there was no body, or the body was form data.
+        */
+       public function getValidatedBody() {
+               return $this->validatedBody;
+       }
+
        /**
         * The subclass should override this to provide the maximum last modified
         * timestamp for the current request. This is called before execute() in
index 34faee2..495b101 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace MediaWiki\Rest\Handler;
 
+use Wikimedia\ParamValidator\ParamValidator;
 use MediaWiki\Rest\SimpleHandler;
 
 /**
@@ -16,4 +17,14 @@ class HelloHandler extends SimpleHandler {
        public function needsWriteAccess() {
                return false;
        }
+
+       public function getParamSettings() {
+               return [
+                       'name' => [
+                               self::PARAM_SOURCE => 'path',
+                               ParamValidator::PARAM_TYPE => 'string',
+                               ParamValidator::PARAM_REQUIRED => true,
+                       ],
+               ];
+       }
 }
index ae6dde2..bcc414f 100644 (file)
@@ -8,7 +8,19 @@ namespace MediaWiki\Rest;
  * error response.
  */
 class HttpException extends \Exception {
-       public function __construct( $message, $code = 500 ) {
+
+       /** @var array|null */
+       private $errorData = null;
+
+       public function __construct( $message, $code = 500, $errorData = null ) {
                parent::__construct( $message, $code );
+               $this->errorData = $errorData;
+       }
+
+       /**
+        * @return array|null
+        */
+       public function getErrorData() {
+               return $this->errorData;
        }
 }
index d18cdb5..5e5a198 100644 (file)
@@ -175,8 +175,13 @@ class ResponseFactory {
        public function createFromException( $exception ) {
                if ( $exception instanceof HttpException ) {
                        // FIXME can HttpException represent 2xx or 3xx responses?
-                       $response = $this->createHttpError( $exception->getCode(),
-                               [ 'message' => $exception->getMessage() ] );
+                       $response = $this->createHttpError(
+                               $exception->getCode(),
+                               array_merge(
+                                       [ 'message' => $exception->getMessage() ],
+                                       (array)$exception->getErrorData()
+                               )
+                       );
                } else {
                        $response = $this->createHttpError( 500, [
                                'message' => 'Error: exception of type ' . get_class( $exception ),
index 961da01..a520130 100644 (file)
@@ -6,6 +6,7 @@ use AppendIterator;
 use BagOStuff;
 use MediaWiki\Rest\BasicAccess\BasicAuthorizerInterface;
 use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
+use MediaWiki\Rest\Validator\Validator;
 use Wikimedia\ObjectFactory;
 
 /**
@@ -44,6 +45,12 @@ class Router {
        /** @var BasicAuthorizerInterface */
        private $basicAuth;
 
+       /** @var ObjectFactory */
+       private $objectFactory;
+
+       /** @var Validator */
+       private $restValidator;
+
        /**
         * @param string[] $routeFiles List of names of JSON files containing routes
         * @param array $extraRoutes Extension route array
@@ -51,10 +58,13 @@ class Router {
         * @param BagOStuff $cacheBag A cache in which to store the matcher trees
         * @param ResponseFactory $responseFactory
         * @param BasicAuthorizerInterface $basicAuth
+        * @param ObjectFactory $objectFactory
+        * @param Validator $restValidator
         */
        public function __construct( $routeFiles, $extraRoutes, $rootPath,
                BagOStuff $cacheBag, ResponseFactory $responseFactory,
-               BasicAuthorizerInterface $basicAuth
+               BasicAuthorizerInterface $basicAuth, ObjectFactory $objectFactory,
+               Validator $restValidator
        ) {
                $this->routeFiles = $routeFiles;
                $this->extraRoutes = $extraRoutes;
@@ -62,6 +72,8 @@ class Router {
                $this->cacheBag = $cacheBag;
                $this->responseFactory = $responseFactory;
                $this->basicAuth = $basicAuth;
+               $this->objectFactory = $objectFactory;
+               $this->restValidator = $restValidator;
        }
 
        /**
@@ -245,9 +257,10 @@ class Router {
                $request->setPathParams( array_map( 'rawurldecode', $match['params'] ) );
                $spec = $match['userData'];
                $objectFactorySpec = array_intersect_key( $spec,
+                       // @todo ObjectFactory supports more keys than this.
                        [ 'factory' => true, 'class' => true, 'args' => true ] );
                /** @var $handler Handler (annotation for PHPStorm) */
-               $handler = ObjectFactory::getObjectFromSpec( $objectFactorySpec );
+               $handler = $this->objectFactory->createObject( $objectFactorySpec );
                $handler->init( $this, $request, $spec, $this->responseFactory );
 
                try {
@@ -268,6 +281,9 @@ class Router {
                if ( $authResult ) {
                        return $this->responseFactory->createHttpError( 403, [ 'error' => $authResult ] );
                }
+
+               $handler->validate( $this->restValidator );
+
                $response = $handler->execute();
                if ( !( $response instanceof ResponseInterface ) ) {
                        $response = $this->responseFactory->createFromReturnValue( $response );
index 3718d66..3c19e48 100644 (file)
@@ -14,7 +14,26 @@ namespace MediaWiki\Rest;
  */
 class SimpleHandler extends Handler {
        public function execute() {
-               $params = array_values( $this->getRequest()->getPathParams() );
+               $paramSettings = $this->getParamSettings();
+               $validatedParams = $this->getValidatedParams();
+               $unvalidatedParams = [];
+               $params = [];
+               foreach ( $this->getRequest()->getPathParams() as $name => $value ) {
+                       $source = $paramSettings[$name][self::PARAM_SOURCE] ?? 'unknown';
+                       if ( $source !== 'path' ) {
+                               $unvalidatedParams[] = $name;
+                               $params[] = $value;
+                       } else {
+                               $params[] = $validatedParams[$name];
+                       }
+               }
+
+               if ( $unvalidatedParams ) {
+                       throw new \LogicException(
+                               'Path parameters were not validated: ' . implode( ', ', $unvalidatedParams )
+                       );
+               }
+
                // @phan-suppress-next-line PhanUndeclaredMethod
                return $this->run( ...$params );
        }
diff --git a/includes/Rest/Validator/BodyValidator.php b/includes/Rest/Validator/BodyValidator.php
new file mode 100644 (file)
index 0000000..0147fa8
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\RequestInterface;
+
+/**
+ * Interface for validating a request body
+ */
+interface BodyValidator {
+
+       /**
+        * Validate the body of a request.
+        *
+        * This may return a data structure representing the parsed body. When used
+        * in the context of Handler::validateParams(), the returned value will be
+        * available to the handler via Handler::getValidatedBody().
+        *
+        * @param RequestInterface $request
+        * @return mixed
+        * @throws HttpException on validation failure
+        */
+       public function validateBody( RequestInterface $request );
+
+}
diff --git a/includes/Rest/Validator/NullBodyValidator.php b/includes/Rest/Validator/NullBodyValidator.php
new file mode 100644 (file)
index 0000000..4fba5fb
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use MediaWiki\Rest\RequestInterface;
+
+/**
+ * Do-nothing body validator
+ */
+class NullBodyValidator implements BodyValidator {
+
+       public function validateBody( RequestInterface $request ) {
+               return null;
+       }
+
+}
diff --git a/includes/Rest/Validator/ParamValidatorCallbacks.php b/includes/Rest/Validator/ParamValidatorCallbacks.php
new file mode 100644 (file)
index 0000000..6c54a50
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use InvalidArgumentException;
+use MediaWiki\Rest\RequestInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use User;
+use Wikimedia\ParamValidator\Callbacks;
+use Wikimedia\ParamValidator\ValidationException;
+
+class ParamValidatorCallbacks implements Callbacks {
+
+       /** @var RequestInterface */
+       private $request;
+
+       /** @var User */
+       private $user;
+
+       public function __construct( RequestInterface $request, User $user ) {
+               $this->request = $request;
+               $this->user = $user;
+       }
+
+       /**
+        * Get the raw parameters from a source in the request
+        * @param string $source 'path', 'query', or 'post'
+        * @return array
+        */
+       private function getParamsFromSource( $source ) {
+               switch ( $source ) {
+                       case 'path':
+                               return $this->request->getPathParams();
+
+                       case 'query':
+                               return $this->request->getQueryParams();
+
+                       case 'post':
+                               return $this->request->getPostParams();
+
+                       default:
+                               throw new InvalidArgumentException( __METHOD__ . ": Invalid source '$source'" );
+               }
+       }
+
+       public function hasParam( $name, array $options ) {
+               $params = $this->getParamsFromSource( $options['source'] );
+               return isset( $params[$name] );
+       }
+
+       public function getValue( $name, $default, array $options ) {
+               $params = $this->getParamsFromSource( $options['source'] );
+               return $params[$name] ?? $default;
+               // @todo Should normalization to NFC UTF-8 be done here (much like in the
+               // action API and the rest of MW), or should it be left to handlers to
+               // do whatever normalization they need?
+       }
+
+       public function hasUpload( $name, array $options ) {
+               if ( $options['source'] !== 'post' ) {
+                       return false;
+               }
+               return $this->getUploadedFile( $name, $options ) !== null;
+       }
+
+       public function getUploadedFile( $name, array $options ) {
+               if ( $options['source'] !== 'post' ) {
+                       return null;
+               }
+               $upload = $this->request->getUploadedFiles()[$name] ?? null;
+               return $upload instanceof UploadedFileInterface ? $upload : null;
+       }
+
+       public function recordCondition( ValidationException $condition, array $options ) {
+               // @todo Figure out how to handle warnings
+       }
+
+       public function useHighLimits( array $options ) {
+               return $this->user->isAllowed( 'apihighlimits' );
+       }
+
+}
diff --git a/includes/Rest/Validator/Validator.php b/includes/Rest/Validator/Validator.php
new file mode 100644 (file)
index 0000000..cee1cdb
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+
+namespace MediaWiki\Rest\Validator;
+
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\RequestInterface;
+use User;
+use Wikimedia\ObjectFactory;
+use Wikimedia\ParamValidator\ParamValidator;
+use Wikimedia\ParamValidator\TypeDef\BooleanDef;
+use Wikimedia\ParamValidator\TypeDef\EnumDef;
+use Wikimedia\ParamValidator\TypeDef\FloatDef;
+use Wikimedia\ParamValidator\TypeDef\IntegerDef;
+use Wikimedia\ParamValidator\TypeDef\PasswordDef;
+use Wikimedia\ParamValidator\TypeDef\StringDef;
+use Wikimedia\ParamValidator\TypeDef\TimestampDef;
+use Wikimedia\ParamValidator\TypeDef\UploadDef;
+use Wikimedia\ParamValidator\ValidationException;
+
+/**
+ * Wrapper for ParamValidator
+ *
+ * It's intended to be used in the REST API classes by composition.
+ *
+ * @since 1.34
+ */
+class Validator {
+
+       /** @var array Type defs for ParamValidator */
+       private static $typeDefs = [
+               'boolean' => [ 'class' => BooleanDef::class ],
+               'enum' => [ 'class' => EnumDef::class ],
+               'integer' => [ 'class' => IntegerDef::class ],
+               'float' => [ 'class' => FloatDef::class ],
+               'double' => [ 'class' => FloatDef::class ],
+               'NULL' => [
+                       'class' => StringDef::class,
+                       'args' => [ [
+                               'allowEmptyWhenRequired' => true,
+                       ] ],
+               ],
+               'password' => [ 'class' => PasswordDef::class ],
+               'string' => [ 'class' => StringDef::class ],
+               'timestamp' => [ 'class' => TimestampDef::class ],
+               'upload' => [ 'class' => UploadDef::class ],
+       ];
+
+       /** @var string[] HTTP request methods that we expect never to have a payload */
+       private static $noBodyMethods = [ 'GET', 'HEAD', 'DELETE' ];
+
+       /** @var string[] HTTP request methods that we expect always to have a payload */
+       private static $bodyMethods = [ 'POST', 'PUT' ];
+
+       /** @var string[] Content types handled via $_POST */
+       private static $formDataContentTypes = [
+               'application/x-www-form-urlencoded',
+               'multipart/form-data',
+       ];
+
+       /** @var ParamValidator */
+       private $paramValidator;
+
+       /**
+        * @internal
+        * @param ObjectFactory $objectFactory
+        * @param RequestInterface $request
+        * @param User $user
+        */
+       public function __construct(
+               ObjectFactory $objectFactory, RequestInterface $request, User $user
+       ) {
+               $this->paramValidator = new ParamValidator(
+                       new ParamValidatorCallbacks( $request, $user ),
+                       $objectFactory,
+                       [
+                               'typeDefs' => self::$typeDefs,
+                       ]
+               );
+       }
+
+       /**
+        * Validate parameters
+        * @param array[] $paramSettings Parameter settings
+        * @return array Validated parameters
+        * @throws HttpException on validaton failure
+        */
+       public function validateParams( array $paramSettings ) {
+               $validatedParams = [];
+               foreach ( $paramSettings as $name => $settings ) {
+                       try {
+                               $validatedParams[$name] = $this->paramValidator->getValue( $name, $settings, [
+                                       'source' => $settings[Handler::PARAM_SOURCE] ?? 'unspecified',
+                               ] );
+                       } catch ( ValidationException $e ) {
+                               throw new HttpException( 'Parameter validation failed', 400, [
+                                       'error' => 'parameter-validation-failed',
+                                       'name' => $e->getParamName(),
+                                       'value' => $e->getParamValue(),
+                                       'failureCode' => $e->getFailureCode(),
+                                       'failureData' => $e->getFailureData(),
+                               ] );
+                       }
+               }
+               return $validatedParams;
+       }
+
+       /**
+        * Validate the body of a request.
+        *
+        * This may return a data structure representing the parsed body. When used
+        * in the context of Handler::validateParams(), the returned value will be
+        * available to the handler via Handler::getValidatedBody().
+        *
+        * @param RequestInterface $request
+        * @param Handler $handler Used to call getBodyValidator()
+        * @return mixed May be null
+        * @throws HttpException on validation failure
+        */
+       public function validateBody( RequestInterface $request, Handler $handler ) {
+               $method = strtoupper( trim( $request->getMethod() ) );
+
+               // If the method should never have a body, don't bother validating.
+               if ( in_array( $method, self::$noBodyMethods, true ) ) {
+                       return null;
+               }
+
+               // Get the content type
+               list( $ct ) = explode( ';', $request->getHeaderLine( 'Content-Type' ), 2 );
+               $ct = strtolower( trim( $ct ) );
+               if ( $ct === '' ) {
+                       // No Content-Type was supplied. RFC 7231 § 3.1.1.5 allows this, but since it's probably a
+                       // client error let's return a 415. But don't 415 for unknown methods and an empty body.
+                       if ( !in_array( $method, self::$bodyMethods, true ) ) {
+                               $body = $request->getBody();
+                               $size = $body->getSize();
+                               if ( $size === null ) {
+                                       // No size available. Try reading 1 byte.
+                                       if ( $body->isSeekable() ) {
+                                               $body->rewind();
+                                       }
+                                       $size = $body->read( 1 ) === '' ? 0 : 1;
+                               }
+                               if ( $size === 0 ) {
+                                       return null;
+                               }
+                       }
+                       throw new HttpException( "A Content-Type header must be supplied with a request payload.", 415, [
+                               'error' => 'no-content-type',
+                       ] );
+               }
+
+               // Form data is parsed into $_POST and $_FILES by PHP and from there is accessed as parameters,
+               // don't bother trying to handle these via BodyValidator too.
+               if ( in_array( $ct, self::$formDataContentTypes, true ) ) {
+                       return null;
+               }
+
+               // Validate the body. BodyValidator throws an HttpException on failure.
+               return $handler->getBodyValidator( $ct )->validateBody( $request );
+       }
+
+}
index c6e727e..292d6ba 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.
index 740377c..0b0aaf5 100644 (file)
@@ -77,9 +77,7 @@ use Wikimedia\ObjectFactory;
 
 return [
        'ActorMigration' => function ( MediaWikiServices $services ) : ActorMigration {
-               return new ActorMigration(
-                       $services->getMainConfig()->get( 'ActorTableSchemaMigrationStage' )
-               );
+               return new ActorMigration( SCHEMA_COMPAT_NEW );
        },
 
        'BadFileLookup' => function ( MediaWikiServices $services ) : BadFileLookup {
@@ -734,7 +732,8 @@ return [
                return new SpecialPageFactory(
                        new ServiceOptions(
                                SpecialPageFactory::$constructorOptions, $services->getMainConfig() ),
-                       $services->getContentLanguage()
+                       $services->getContentLanguage(),
+                       $services->getObjectFactory()
                );
        },
 
index d629021..39f0c81 100644 (file)
@@ -52,6 +52,17 @@ if ( ini_get( 'mbstring.func_overload' ) ) {
        die( 'MediaWiki does not support installations where mbstring.func_overload is non-zero.' );
 }
 
+// Define MW_ENTRY_POINT if it's not already, so that config code can check the
+// value without using defined()
+if ( !defined( 'MW_ENTRY_POINT' ) ) {
+       /**
+        * The entry point, which may be either the script filename without the
+        * file extension, or "cli" for maintenance scripts, or "unknown" for any
+        * entry point that does not set the constant.
+        */
+       define( 'MW_ENTRY_POINT', 'unknown' );
+}
+
 // Start the autoloader, so that extensions can derive classes from core files
 require_once "$IP/includes/AutoLoader.php";
 
@@ -156,12 +167,6 @@ if ( $wgArticlePath === false ) {
        }
 }
 
-if ( !empty( $wgActionPaths ) && !isset( $wgActionPaths['view'] ) ) {
-       // 'view' is assumed the default action path everywhere in the code
-       // but is rarely filled in $wgActionPaths
-       $wgActionPaths['view'] = $wgArticlePath;
-}
-
 if ( $wgResourceBasePath === null ) {
        $wgResourceBasePath = $wgScriptPath;
 }
@@ -537,12 +542,6 @@ if ( isset( $wgSquidMaxage ) ) {
        $wgSquidMaxage = $wgCdnMaxAge;
 }
 
-// Easy to forget to falsify $wgDebugToolbar for static caches.
-// If file cache or CDN cache is on, just disable this (DWIMD).
-if ( $wgUseFileCache || $wgUseCdn ) {
-       $wgDebugToolbar = false;
-}
-
 // Blacklisted file extensions shouldn't appear on the "allowed" list
 $wgFileExtensions = array_values( array_diff( $wgFileExtensions, $wgFileBlacklist ) );
 
@@ -611,12 +610,7 @@ if ( defined( 'MW_NO_SESSION' ) ) {
        $wgPHPSessionHandling = MW_NO_SESSION === 'warn' ? 'warn' : 'disable';
 }
 
-// Disable MWDebug for command line mode, this prevents MWDebug from eating up
-// all the memory from logging SQL queries on maintenance scripts
-global $wgCommandLineMode;
-if ( $wgDebugToolbar && !$wgCommandLineMode ) {
-       MWDebug::init();
-}
+MWDebug::setup();
 
 // Reset the global service locator, so any services that have already been created will be
 // re-created while taking into account any custom settings and extensions.
index 547b28c..1e93c44 100644 (file)
@@ -51,10 +51,11 @@ class Title implements LinkTarget, IDBAccessObject {
        const CACHE_MAX = 1000;
 
        /**
-        * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends
-        * to use the master DB
+        * Used to be GAID_FOR_UPDATE define(). Used with getArticleID() and friends
+        * to use the master DB and inject it into link cache.
+        * @deprecated since 1.34, use Title::READ_LATEST instead.
         */
-       const GAID_FOR_UPDATE = 1;
+       const GAID_FOR_UPDATE = 512;
 
        /**
         * Flag for use with factory methods like newFromLinkTarget() that have
@@ -74,25 +75,18 @@ class Title implements LinkTarget, IDBAccessObject {
 
        /** @var string Text form (spaces not underscores) of the main part */
        public $mTextform = '';
-
        /** @var string URL-encoded form of the main part */
        public $mUrlform = '';
-
        /** @var string Main part with underscores */
        public $mDbkeyform = '';
-
        /** @var string Database key with the initial letter in the case specified by the user */
        protected $mUserCaseDBKey;
-
        /** @var int Namespace index, i.e. one of the NS_xxxx constants */
        public $mNamespace = NS_MAIN;
-
        /** @var string Interwiki prefix */
        public $mInterwiki = '';
-
        /** @var bool Was this Title created from a string with a local interwiki prefix? */
        private $mLocalInterwiki = false;
-
        /** @var string Title fragment (i.e. the bit after the #) */
        public $mFragment = '';
 
@@ -467,16 +461,18 @@ class Title implements LinkTarget, IDBAccessObject {
         * Create a new Title from an article ID
         *
         * @param int $id The page_id corresponding to the Title to create
-        * @param int $flags Use Title::GAID_FOR_UPDATE to use master
+        * @param int $flags Bitfield of class READ_* constants
         * @return Title|null The new object, or null on an error
         */
        public static function newFromID( $id, $flags = 0 ) {
-               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
-               $row = $db->selectRow(
+               $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
+               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+               $row = wfGetDB( $index )->selectRow(
                        'page',
                        self::getSelectFields(),
                        [ 'page_id' => $id ],
-                       __METHOD__
+                       __METHOD__,
+                       $options
                );
                if ( $row !== false ) {
                        $title = self::newFromRow( $row );
@@ -545,10 +541,10 @@ class Title implements LinkTarget, IDBAccessObject {
                        if ( isset( $row->page_latest ) ) {
                                $this->mLatestID = (int)$row->page_latest;
                        }
-                       if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
-                               $this->mContentModel = (string)$row->page_content_model;
-                       } elseif ( !$this->mForcedContentModel ) {
-                               $this->mContentModel = false; # initialized lazily in getContentModel()
+                       if ( isset( $row->page_content_model ) ) {
+                               $this->lazyFillContentModel( $row->page_content_model );
+                       } else {
+                               $this->lazyFillContentModel( false ); // lazily-load getContentModel()
                        }
                        if ( isset( $row->page_lang ) ) {
                                $this->mDbPageLanguage = (string)$row->page_lang;
@@ -561,9 +557,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
-                       if ( !$this->mForcedContentModel ) {
-                               $this->mContentModel = false; # initialized lazily in getContentModel()
-                       }
+                       $this->lazyFillContentModel( false ); // lazily-load getContentModel()
                }
        }
 
@@ -598,7 +592,6 @@ class Title implements LinkTarget, IDBAccessObject {
                $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
                $t->mUrlform = wfUrlencode( $t->mDbkeyform );
                $t->mTextform = strtr( $title, '_', ' ' );
-               $t->mContentModel = false; # initialized lazily in getContentModel()
                return $t;
        }
 
@@ -676,7 +669,7 @@ class Title implements LinkTarget, IDBAccessObject {
         * Get the prefixed DB key associated with an ID
         *
         * @param int $id The page_id of the article
-        * @return Title|null An object representing the article, or null if no such article was found
+        * @return string|null An object representing the article, or null if no such article was found
         */
        public static function nameOf( $id ) {
                $dbr = wfGetDB( DB_REPLICA );
@@ -691,8 +684,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        return null;
                }
 
-               $n = self::makeName( $s->page_namespace, $s->page_title );
-               return $n;
+               return self::makeName( $s->page_namespace, $s->page_title );
        }
 
        /**
@@ -1051,21 +1043,31 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @todo Deprecate this in favor of SlotRecord::getModel()
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return string Content model id
         */
        public function getContentModel( $flags = 0 ) {
-               if ( !$this->mForcedContentModel
-                       && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
-                       && $this->getArticleID( $flags )
+               if ( $this->mForcedContentModel ) {
+                       if ( !$this->mContentModel ) {
+                               throw new RuntimeException( 'Got out of sync; an empty model is being forced' );
+                       }
+                       // Content model is locked to the currently loaded one
+                       return $this->mContentModel;
+               }
+
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->lazyFillContentModel( $this->loadFieldFromDB( 'page_content_model', $flags ) );
+               } elseif (
+                       ( !$this->mContentModel || $flags & self::GAID_FOR_UPDATE ) &&
+                       $this->getArticleId( $flags )
                ) {
                        $linkCache = MediaWikiServices::getInstance()->getLinkCache();
                        $linkCache->addLinkObj( $this ); # in case we already had an article ID
-                       $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
+                       $this->lazyFillContentModel( $linkCache->getGoodLinkFieldObj( $this, 'model' ) );
                }
 
                if ( !$this->mContentModel ) {
-                       $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
+                       $this->lazyFillContentModel( ContentHandler::getDefaultModelFor( $this ) );
                }
 
                return $this->mContentModel;
@@ -1082,21 +1084,38 @@ class Title implements LinkTarget, IDBAccessObject {
        }
 
        /**
-        * Set a proposed content model for the page for permissions
-        * checking. This does not actually change the content model
-        * of a title!
+        * Set a proposed content model for the page for permissions checking
+        *
+        * This does not actually change the content model of a title in the DB.
+        * It only affects this particular Title instance. The content model is
+        * forced to remain this value until another setContentModel() call.
         *
-        * Additionally, you should make sure you've checked
-        * ContentHandler::canBeUsedOn() first.
+        * ContentHandler::canBeUsedOn() should be checked before calling this
+        * if there is any doubt regarding the applicability of the content model
         *
         * @since 1.28
         * @param string $model CONTENT_MODEL_XXX constant
         */
        public function setContentModel( $model ) {
+               if ( (string)$model === '' ) {
+                       throw new InvalidArgumentException( "Missing CONTENT_MODEL_* constant" );
+               }
+
                $this->mContentModel = $model;
                $this->mForcedContentModel = true;
        }
 
+       /**
+        * If the content model field is not frozen then update it with a retreived value
+        *
+        * @param string|bool $model CONTENT_MODEL_XXX constant or false
+        */
+       private function lazyFillContentModel( $model ) {
+               if ( !$this->mForcedContentModel ) {
+                       $this->mContentModel = ( $model === false ) ? false : (string)$model;
+               }
+       }
+
        /**
         * Get the namespace text
         *
@@ -2068,16 +2087,18 @@ class Title implements LinkTarget, IDBAccessObject {
                                $url = false;
                                $matches = [];
 
-                               if ( !empty( $wgActionPaths )
+                               $articlePaths = PathRouter::getActionPaths( $wgActionPaths, $wgArticlePath );
+
+                               if ( $articlePaths
                                        && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
                                ) {
                                        $action = urldecode( $matches[2] );
-                                       if ( isset( $wgActionPaths[$action] ) ) {
+                                       if ( isset( $articlePaths[$action] ) ) {
                                                $query = $matches[1];
                                                if ( isset( $matches[4] ) ) {
                                                        $query .= $matches[4];
                                                }
-                                               $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
+                                               $url = str_replace( '$1', $dbkey, $articlePaths[$action] );
                                                if ( $query != '' ) {
                                                        $url = wfAppendQuery( $url, $query );
                                                }
@@ -2796,10 +2817,7 @@ class Title implements LinkTarget, IDBAccessObject {
                        return;
                }
 
-               // TODO: should probably pass $flags into getArticleID, but it seems hacky
-               // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
-               // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
-               $id = $this->getArticleID();
+               $id = $this->getArticleID( $flags );
                if ( $id ) {
                        $fname = __METHOD__;
                        $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
@@ -3023,24 +3041,28 @@ class Title implements LinkTarget, IDBAccessObject {
         * Get the article ID for this Title from the link cache,
         * adding it if necessary
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select
-        *  for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return int The ID
         */
        public function getArticleID( $flags = 0 ) {
                if ( $this->mNamespace < 0 ) {
                        $this->mArticleID = 0;
+
                        return $this->mArticleID;
                }
+
                $linkCache = MediaWikiServices::getInstance()->getLinkCache();
                if ( $flags & self::GAID_FOR_UPDATE ) {
                        $oldUpdate = $linkCache->forUpdate( true );
                        $linkCache->clearLink( $this );
                        $this->mArticleID = $linkCache->addLinkObj( $this );
                        $linkCache->forUpdate( $oldUpdate );
+               } elseif ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mArticleID = (int)$this->loadFieldFromDB( 'page_id', $flags );
                } elseif ( $this->mArticleID == -1 ) {
                        $this->mArticleID = $linkCache->addLinkObj( $this );
                }
+
                return $this->mArticleID;
        }
 
@@ -3048,33 +3070,27 @@ class Title implements LinkTarget, IDBAccessObject {
         * Is this an article that is a redirect page?
         * Uses link cache, adding it if necessary
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return bool
         */
        public function isRedirect( $flags = 0 ) {
-               if ( !is_null( $this->mRedirect ) ) {
-                       return $this->mRedirect;
-               }
-               if ( !$this->getArticleID( $flags ) ) {
-                       $this->mRedirect = false;
-                       return $this->mRedirect;
-               }
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mRedirect = (bool)$this->loadFieldFromDB( 'page_is_redirect', $flags );
+               } else {
+                       if ( $this->mRedirect !== null ) {
+                               return $this->mRedirect;
+                       } elseif ( !$this->getArticleID( $flags ) ) {
+                               $this->mRedirect = false;
 
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->addLinkObj( $this ); # in case we already had an article ID
-               $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
-               if ( $cached === null ) {
-                       # Trust LinkCache's state over our own
-                       # LinkCache is telling us that the page doesn't exist, despite there being cached
-                       # data relating to an existing page in $this->mArticleID. Updaters should clear
-                       # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
-                       # set, then LinkCache will definitely be up to date here, since getArticleID() forces
-                       # LinkCache to refresh its data from the master.
-                       $this->mRedirect = false;
-                       return $this->mRedirect;
-               }
+                               return $this->mRedirect;
+                       }
 
-               $this->mRedirect = (bool)$cached;
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
+                       $linkCache->addLinkObj( $this ); // in case we already had an article ID
+                       // Note that LinkCache returns null if it thinks the page does not exist;
+                       // always trust the state of LinkCache over that of this Title instance.
+                       $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+               }
 
                return $this->mRedirect;
        }
@@ -3083,27 +3099,26 @@ class Title implements LinkTarget, IDBAccessObject {
         * What is the length of this page?
         * Uses link cache, adding it if necessary
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return int
         */
        public function getLength( $flags = 0 ) {
-               if ( $this->mLength != -1 ) {
-                       return $this->mLength;
-               }
-               if ( !$this->getArticleID( $flags ) ) {
-                       $this->mLength = 0;
-                       return $this->mLength;
-               }
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->addLinkObj( $this ); # in case we already had an article ID
-               $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
-               if ( $cached === null ) {
-                       # Trust LinkCache's state over our own, as for isRedirect()
-                       $this->mLength = 0;
-                       return $this->mLength;
-               }
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mLength = (int)$this->loadFieldFromDB( 'page_len', $flags );
+               } else {
+                       if ( $this->mLength != -1 ) {
+                               return $this->mLength;
+                       } elseif ( !$this->getArticleID( $flags ) ) {
+                               $this->mLength = 0;
+                               return $this->mLength;
+                       }
 
-               $this->mLength = intval( $cached );
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
+                       $linkCache->addLinkObj( $this ); // in case we already had an article ID
+                       // Note that LinkCache returns null if it thinks the page does not exist;
+                       // always trust the state of LinkCache over that of this Title instance.
+                       $this->mLength = (int)$linkCache->getGoodLinkFieldObj( $this, 'length' );
+               }
 
                return $this->mLength;
        }
@@ -3111,49 +3126,46 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * What is the page_latest field for this page?
         *
-        * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return int Int or 0 if the page doesn't exist
         */
        public function getLatestRevID( $flags = 0 ) {
-               if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
-                       return intval( $this->mLatestID );
-               }
-               if ( !$this->getArticleID( $flags ) ) {
-                       $this->mLatestID = 0;
-                       return $this->mLatestID;
-               }
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->addLinkObj( $this ); # in case we already had an article ID
-               $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
-               if ( $cached === null ) {
-                       # Trust LinkCache's state over our own, as for isRedirect()
-                       $this->mLatestID = 0;
-                       return $this->mLatestID;
-               }
+               if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
+                       $this->mLatestID = (int)$this->loadFieldFromDB( 'page_latest', $flags );
+               } else {
+                       if ( $this->mLatestID !== false ) {
+                               return (int)$this->mLatestID;
+                       } elseif ( !$this->getArticleID( $flags ) ) {
+                               $this->mLatestID = 0;
+
+                               return $this->mLatestID;
+                       }
 
-               $this->mLatestID = intval( $cached );
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
+                       $linkCache->addLinkObj( $this ); // in case we already had an article ID
+                       // Note that LinkCache returns null if it thinks the page does not exist;
+                       // always trust the state of LinkCache over that of this Title instance.
+                       $this->mLatestID = (int)$linkCache->getGoodLinkFieldObj( $this, 'revision' );
+               }
 
                return $this->mLatestID;
        }
 
        /**
-        * This clears some fields in this object, and clears any associated
-        * keys in the "bad links" section of the link cache.
+        * Inject a page ID, reset DB-loaded fields, and clear the link cache for this title
+        *
+        * This can be called on page insertion to allow loading of the new page_id without
+        * having to create a new Title instance. Likewise with deletion.
         *
-        * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow
-        * loading of the new page_id. It's also called from
-        * WikiPage::doDeleteArticleReal()
+        * @note This overrides Title::setContentModel()
         *
-        * @param int $newid The new Article ID
+        * @param int|bool $id Page ID, 0 for non-existant, or false for "unknown" (lazy-load)
         */
-       public function resetArticleID( $newid ) {
-               $linkCache = MediaWikiServices::getInstance()->getLinkCache();
-               $linkCache->clearLink( $this );
-
-               if ( $newid === false ) {
+       public function resetArticleID( $id ) {
+               if ( $id === false ) {
                        $this->mArticleID = -1;
                } else {
-                       $this->mArticleID = intval( $newid );
+                       $this->mArticleID = (int)$id;
                }
                $this->mRestrictionsLoaded = false;
                $this->mRestrictions = [];
@@ -3162,10 +3174,13 @@ class Title implements LinkTarget, IDBAccessObject {
                $this->mLength = -1;
                $this->mLatestID = false;
                $this->mContentModel = false;
+               $this->mForcedContentModel = false;
                $this->mEstimateRevisions = null;
                $this->mPageLanguage = null;
                $this->mDbPageLanguage = false;
                $this->mIsBigDeletion = null;
+
+               MediaWikiServices::getInstance()->getLinkCache()->clearLink( $this );
        }
 
        public static function clearCaches() {
@@ -3499,6 +3514,7 @@ class Title implements LinkTarget, IDBAccessObject {
 
                $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $this, $nt );
                $method = $auth ? 'moveIfAllowed' : 'move';
+               /** @var Status $status */
                $status = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
                if ( $status->isOK() ) {
                        return true;
@@ -3531,6 +3547,7 @@ class Title implements LinkTarget, IDBAccessObject {
 
                $mp = new MovePage( $this, $nt );
                $method = $auth ? 'moveSubpagesIfAllowed' : 'moveSubpages';
+               /** @var Status $result */
                $result = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
 
                if ( !$result->isOK() ) {
@@ -3539,6 +3556,7 @@ class Title implements LinkTarget, IDBAccessObject {
 
                $retval = [];
                foreach ( $result->getValue() as $key => $status ) {
+                       /** @var Status $status */
                        if ( $status->isOK() ) {
                                $retval[$key] = $status->getValue();
                        } else {
@@ -3549,8 +3567,9 @@ class Title implements LinkTarget, IDBAccessObject {
        }
 
        /**
-        * Checks if this page is just a one-rev redirect.
-        * Adds lock, so don't use just for light purposes.
+        * Locks the page row and check if this page is single revision redirect
+        *
+        * This updates the cached fields of this instance via Title::loadFromRow()
         *
         * @return bool
         */
@@ -3730,24 +3749,22 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get next/previous revision ID relative to another revision ID
         * @param int $revId Revision ID. Get the revision that was before this one.
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @param string $dir 'next' or 'prev'
         * @return int|bool New revision ID, or false if none exists
         */
        private function getRelativeRevisionID( $revId, $flags, $dir ) {
                $rl = MediaWikiServices::getInstance()->getRevisionLookup();
-               $rlFlags = $flags === self::GAID_FOR_UPDATE ? IDBAccessObject::READ_LATEST : 0;
-               $rev = $rl->getRevisionById( $revId, $rlFlags );
+               $rev = $rl->getRevisionById( $revId, $flags );
                if ( !$rev ) {
                        return false;
                }
-               $oldRev = $dir === 'next'
-                       ? $rl->getNextRevision( $rev, $rlFlags )
-                       : $rl->getPreviousRevision( $rev, $rlFlags );
-               if ( !$oldRev ) {
-                       return false;
-               }
-               return $oldRev->getId();
+
+               $oldRev = ( $dir === 'next' )
+                       ? $rl->getNextRevision( $rev, $flags )
+                       : $rl->getPreviousRevision( $rev, $flags );
+
+               return $oldRev ? $oldRev->getId() : false;
        }
 
        /**
@@ -3755,7 +3772,7 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @deprecated since 1.34, use RevisionLookup::getPreviousRevision
         * @param int $revId Revision ID. Get the revision that was before this one.
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return int|bool Old revision ID, or false if none exists
         */
        public function getPreviousRevisionID( $revId, $flags = 0 ) {
@@ -3767,7 +3784,7 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @deprecated since 1.34, use RevisionLookup::getNextRevision
         * @param int $revId Revision ID. Get the revision that was after this one.
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return int|bool Next revision ID, or false if none exists
         */
        public function getNextRevisionID( $revId, $flags = 0 ) {
@@ -3777,21 +3794,26 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get the first revision of the page
         *
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return Revision|null If page doesn't exist
         */
        public function getFirstRevision( $flags = 0 ) {
                $pageId = $this->getArticleID( $flags );
                if ( $pageId ) {
-                       $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
+                       $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
+                       list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
                        $revQuery = Revision::getQueryInfo();
-                       $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
+                       $row = wfGetDB( $index )->selectRow(
+                               $revQuery['tables'], $revQuery['fields'],
                                [ 'rev_page' => $pageId ],
                                __METHOD__,
-                               [
-                                       'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
-                                       'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
-                               ],
+                               array_merge(
+                                       [
+                                               'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
+                                               'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
+                                       ],
+                                       $options
+                               ),
                                $revQuery['joins']
                        );
                        if ( $row ) {
@@ -3804,7 +3826,7 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get the oldest revision timestamp of this page
         *
-        * @param int $flags Title::GAID_FOR_UPDATE
+        * @param int $flags Bitfield of class READ_* constants
         * @return string|null MW timestamp
         */
        public function getEarliestRevTime( $flags = 0 ) {
@@ -4035,8 +4057,7 @@ class Title implements LinkTarget, IDBAccessObject {
         * If you want to know if a title can be meaningfully viewed, you should
         * probably call the isKnown() method instead.
         *
-        * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check
-        *   from master/for update
+        * @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
         * @return bool
         */
        public function exists( $flags = 0 ) {
@@ -4632,6 +4653,27 @@ class Title implements LinkTarget, IDBAccessObject {
                return $notices;
        }
 
+       /**
+        * @param int $flags Bitfield of class READ_* constants
+        * @return string|bool
+        */
+       private function loadFieldFromDB( $field, $flags ) {
+               if ( !in_array( $field, self::getSelectFields(), true ) ) {
+                       return false; // field does not exist
+               }
+
+               $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
+               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+
+               return wfGetDB( $index )->selectField(
+                       'page',
+                       $field,
+                       $this->pageCond(),
+                       __METHOD__,
+                       $options
+               );
+       }
+
        /**
         * @return array
         */
index 9b8f5a6..a48d032 100644 (file)
@@ -40,9 +40,28 @@ use Wikimedia\AtEase\AtEase;
  * @ingroup HTTP
  */
 class WebRequest {
-       /** @var array */
+       /**
+        * The parameters from $_GET, $_POST and the path router
+        * @var array
+        */
        protected $data;
-       /** @var array */
+
+       /**
+        * The parameters from $_GET. The parameters from the path router are
+        * added by interpolateTitle() during Setup.php.
+        * @var array
+        */
+       protected $queryAndPathParams;
+
+       /**
+        * The parameters from $_GET only.
+        */
+       protected $queryParams;
+
+       /**
+        * Lazy-initialized request headers indexed by upper-case header name
+        * @var array
+        */
        protected $headers = [];
 
        /**
@@ -100,6 +119,8 @@ class WebRequest {
                // POST overrides GET data
                // We don't use $_REQUEST here to avoid interference from cookies...
                $this->data = $_POST + $_GET;
+
+               $this->queryAndPathParams = $this->queryParams = $_GET;
        }
 
        /**
@@ -162,8 +183,9 @@ class WebRequest {
                        }
 
                        global $wgActionPaths;
-                       if ( $wgActionPaths ) {
-                               $router->add( $wgActionPaths, [ 'action' => '$key' ] );
+                       $articlePaths = PathRouter::getActionPaths( $wgActionPaths, $wgArticlePath );
+                       if ( $articlePaths ) {
+                               $router->add( $articlePaths, [ 'action' => '$key' ] );
                        }
 
                        global $wgVariantArticlePath;
@@ -335,7 +357,7 @@ class WebRequest {
 
                $matches = self::getPathInfo( 'title' );
                foreach ( $matches as $key => $val ) {
-                       $this->data[$key] = $_GET[$key] = $_REQUEST[$key] = $val;
+                       $this->data[$key] = $this->queryAndPathParams[$key] = $val;
                }
        }
 
@@ -667,14 +689,27 @@ class WebRequest {
        }
 
        /**
-        * Get the values passed in the query string.
+        * Get the values passed in the query string and the path router parameters.
         * No transformation is performed on the values.
         *
         * @codeCoverageIgnore
         * @return array
         */
        public function getQueryValues() {
-               return $_GET;
+               return $this->queryAndPathParams;
+       }
+
+       /**
+        * Get the values passed in the query string only, not including the path
+        * router parameters. This is less suitable for self-links to index.php but
+        * useful for other entry points. No transformation is performed on the
+        * values.
+        *
+        * @since 1.34
+        * @return array
+        */
+       public function getQueryValuesOnly() {
+               return $this->queryParams;
        }
 
        /**
index 7bcfc88..207721e 100644 (file)
@@ -743,8 +743,6 @@ class InfoAction extends FormlessAction {
                        self::getCacheKey( $cache, $page->getTitle(), $page->getLatest() ),
                        WANObjectCache::TTL_WEEK,
                        function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname, $services ) {
-                               global $wgActorTableSchemaMigrationStage;
-
                                $title = $page->getTitle();
                                $id = $title->getArticleID();
 
@@ -752,19 +750,11 @@ class InfoAction extends FormlessAction {
                                $dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
                                $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
 
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                                       $tables = [ 'revision_actor_temp' ];
-                                       $field = 'revactor_actor';
-                                       $pageField = 'revactor_page';
-                                       $tsField = 'revactor_timestamp';
-                                       $joins = [];
-                               } else {
-                                       $tables = [ 'revision' ];
-                                       $field = 'rev_user_text';
-                                       $pageField = 'rev_page';
-                                       $tsField = 'rev_timestamp';
-                                       $joins = [];
-                               }
+                               $tables = [ 'revision_actor_temp' ];
+                               $field = 'revactor_actor';
+                               $pageField = 'revactor_page';
+                               $tsField = 'revactor_timestamp';
+                               $joins = [];
 
                                $watchedItemStore = $services->getWatchedItemStore();
 
index 0cd9806..056d10c 100644 (file)
@@ -992,7 +992,7 @@ abstract class ApiBase extends ContextSource {
                        return;
                }
 
-               $queryValues = $this->getRequest()->getQueryValues();
+               $queryValues = $this->getRequest()->getQueryValuesOnly();
                $badParams = [];
                foreach ( $params as $param ) {
                        if ( $prefix !== 'noprefix' ) {
index a5e7437..4b74a3d 100644 (file)
@@ -76,10 +76,6 @@ class ApiExpandTemplates extends ApiBase {
                                        $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
                                                wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
                                }
-                       } else {
-                               // Consider the title derived from the revid as having
-                               // been provided.
-                               $titleProvided = true;
                        }
                }
 
index a9fe258..f0e0077 100644 (file)
@@ -293,7 +293,6 @@ class ApiMain extends ApiBase {
                $this->mEnableWrite = $enableWrite;
 
                $this->mCdnMaxAge = -1; // flag for executeActionWithErrorHandling()
-               $this->mCommit = false;
        }
 
        /**
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 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 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 b72d519..628edfa 100644 (file)
                        "Lucas Werkmeister (WMDE)"
                ]
        },
-       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MediaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes à l’API, voyez [[Special:ApiSandbox]].</p>",
+       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MediaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes à l’API, voyez [[Special:ApiSandbox]].</p>",
        "apihelp-main-param-action": "Quelle action effectuer.",
        "apihelp-main-param-format": "Le format de sortie.",
-       "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MédiaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manuel: paramètre Maxlag]] pour plus d’information.",
+       "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MediaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manuel: paramètre Maxlag]] pour plus d’information.",
        "apihelp-main-param-smaxage": "Fixer l’entête HTTP de contrôle de cache <code>s-maxage</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
        "apihelp-main-param-maxage": "Fixer l’entête HTTP de contrôle de cache <code>max-age</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
        "apihelp-main-param-assert": "Vérifier si l’utilisateur est connecté si la valeur est <kbd>user</kbd>, ou s’il a le droit d’un utilisateur robot si la valeur est <kbd>bot</kbd>.",
@@ -51,7 +51,7 @@
        "apihelp-main-param-responselanginfo": "Inclure les langues utilisées pour <var>uselang</var> et <var>errorlang</var> dans le résultat.",
        "apihelp-main-param-origin": "En accédant à l’API en utilisant une requête AJAX inter-domaines (CORS), mettre le domaine d’origine dans ce paramètre. Il doit être inclus dans toute requête de pre-flight, et doit donc faire partie de l’URI de la requête (pas du corps du POST).\n\nPour les requêtes authentifiées, il doit correspondre exactement à une des origines dans l’entête <code>Origin</code> header, donc il doit être fixé avec quelque chose comme <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Si ce paramètre ne correspond pas à l’entête <code>Origin</code>, une réponse 403 sera renvoyée. Si ce paramètre correspond à l’entête <code>Origin</code> et que l’origine est en liste blanche, des entêtes <code>Access-Control-Allow-Origin</code> et <code>Access-Control-Allow-Credentials</code> seront positionnés.\n\nPour les requêtes non authentifiées, spécifiez la valeur <kbd>*</kbd>. Cela positionnera l’entête <code>Access-Control-Allow-Origin</code>, mais <code>Access-Control-Allow-Credentials</code> vaudra <code>false</code> et toutes les données spécifiques à l’utilisateur seront filtrées.",
        "apihelp-main-param-uselang": "Langue à utiliser pour les traductions de message. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd> renvoie une liste de codes de langue, ou en spécifiant <kbd>user</kbd> pour utiliser la préférence de langue de l’utilisateur actuel, ou en spécifiant <kbd>content</kbd> pour utiliser le langage du contenu de ce wiki.",
-       "apihelp-main-param-errorformat": "Format à utiliser pour la sortie du texte d’avertissement et d’erreur.\n; plaintext: Wikitexte avec balises HTML supprimées et les entités remplacées.\n; wikitext: wikitexte non analysé.\n; html: HTML.\n; raw: Clé de message et paramètres.\n; none: Aucune sortie de texte, uniquement les codes erreur.\n; bc: Format utilisé avant MédiaWiki 1.29. <var>errorlang</var> et <var>errorsuselocal</var> sont ignorés.",
+       "apihelp-main-param-errorformat": "Format à utiliser pour la sortie du texte d’avertissement et d’erreur.\n; plaintext: Wikitexte avec balises HTML supprimées et les entités remplacées.\n; wikitext: wikitexte non analysé.\n; html: HTML.\n; raw: Clé de message et paramètres.\n; none: Aucune sortie de texte, uniquement les codes erreur.\n; bc: Format utilisé avant MediaWiki 1.29. <var>errorlang</var> et <var>errorsuselocal</var> sont ignorés.",
        "apihelp-main-param-errorlang": "Langue à utiliser pour les avertissements et les erreurs. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd> renvoyant une liste de codes de langue, ou spécifier <kbd>content</kbd> pour utiliser la langue du contenu de ce wiki, ou spécifier <kbd>uselang</kbd> pour utiliser la même valeur que le paramètre <var>uselang</var>.",
        "apihelp-main-param-errorsuselocal": "S’il est fourni, les textes d’erreur utiliseront des messages adaptés à la langue dans l’espace de noms {{ns:MediaWiki}}.",
        "apihelp-block-summary": "Bloquer un utilisateur.",
@@ -86,7 +86,7 @@
        "apihelp-clientlogin-example-login": "Commencer le processus de connexion au wiki en tant qu’utilisateur <kbd>Exemple</kbd> avec le mot de passe <kbd>ExempleMotDePasse</kbd>.",
        "apihelp-clientlogin-example-login2": "Continuer la connexion après une réponse de l’<samp>IHM</samp> pour l’authentification à deux facteurs, en fournissant un <var>OATHToken</var> valant <kbd>987654</kbd>.",
        "apihelp-compare-summary": "Obtenir la différence entre deux pages.",
-       "apihelp-compare-extended-description": "Vous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
+       "apihelp-compare-extended-description": "Vous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
        "apihelp-compare-param-fromtitle": "Premier titre à comparer.",
        "apihelp-compare-param-fromid": "ID de la première page à comparer.",
        "apihelp-compare-param-fromrev": "Première révision à comparer.",
        "apihelp-edit-param-summary": "Modifier le résumé. Également le titre de la section quand $1section=new et $1sectiontitle n’est pas défini.",
        "apihelp-edit-param-tags": "Modifier les balises à appliquer à la version.",
        "apihelp-edit-param-minor": "Marquer cette modification comme étant mineure.",
-       "apihelp-edit-param-notminor": "Ne pas marquer cette modification comme mineure, même si la préférence utilisateur « {{int:tog-minordefault}} » est positionnée.",
+       "apihelp-edit-param-notminor": "Ne pas marquer cette modification comme mineure, même si la préférence utilisateur « {{int:tog-minordefault}} » est positionnée.",
        "apihelp-edit-param-bot": "Marquer cette modification comme effectuée par un robot.",
        "apihelp-edit-param-basetimestamp": "Horodatage de la révision de base, utilisé pour détecter les conflits de modification. Peut être obtenu via [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
        "apihelp-edit-param-starttimestamp": "L'horodatage, lorsque le processus d'édition est démarré, est utilisé pour détecter les conflits de modification. Une valeur appropriée peut être obtenue en utilisant <var>[[Special:ApiHelp/main|curtimestamp]]</var> lors du démarrage du processus d'édition (par ex. en chargeant le contenu de la page à modifier).",
        "apihelp-opensearch-param-limit": "Nombre maximal de résultats à renvoyer.",
        "apihelp-opensearch-param-namespace": "Espaces de nom à rechercher.  Ignoré if <var>$1search</var> commence avec le préfixe d'un espace de noms valide.",
        "apihelp-opensearch-param-suggest": "Ne rien faire si <var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> vaut faux.",
-       "apihelp-opensearch-param-redirects": "Comment gérer les redirections :\n;return:Renvoie la redirection elle-même.\n;resolve:Renvoie la page cible. Peut renvoyer moins de $1limit résultats.\nPour des raisons historiques, la valeur par défaut est « return » pour $1format=json et « resolve » pour les autres formats.",
+       "apihelp-opensearch-param-redirects": "Comment gérer les redirections :\n;return:Renvoie la redirection elle-même.\n;resolve:Renvoie la page cible. Peut renvoyer moins de $1limit résultats.\nPour des raisons historiques, la valeur par défaut est « return » pour $1format=json et « resolve » pour les autres formats.",
        "apihelp-opensearch-param-format": "Le format de sortie.",
        "apihelp-opensearch-param-warningsaserror": "Si des avertissements apparaissent avec <kbd>format=json</kbd>, renvoyer une erreur d’API au lieu de les ignorer.",
        "apihelp-opensearch-example-te": "Trouver les pages commençant par <kbd>Te</kbd>.",
        "apihelp-parse-param-effectivelanglinks": "Inclut les liens de langue fournis par les extensions (à utiliser avec <kbd>$1prop=langlinks</kbd>).",
        "apihelp-parse-param-section": "Traiter uniquement le contenu de la section ayant ce numéro.\n\nQuand la valeur est <kbd>new</kbd>, traite <var>$1text</var> et <var>$1sectiontitle</var> comme s’ils correspondaient à une nouvelle section de la page.\n\nLa valeur <kbd>new</kbd> n’est autorisée que si <var>text</var> est défini.",
        "apihelp-parse-param-sectiontitle": "Nouveau titre de section quand <var>section</var> vaut <kbd>nouveau</kbd>.\n\nÀ la différence de la modification de page, cela ne revient pas à <var>summary</var> quand il est omis ou vide.",
-       "apihelp-parse-param-disablelimitreport": "Omettre le rapport de limite (« rapport de limite du nouveau PP ») de la sortie de l’analyseur.",
+       "apihelp-parse-param-disablelimitreport": "Omettre le rapport de limite (« rapport de limite du nouveau PP ») de la sortie de l’analyseur.",
        "apihelp-parse-param-disablepp": "Utiliser <var>$1disablelimitreport</var> à la place.",
        "apihelp-parse-param-disableeditsection": "Omettre les liens de modification de section de la sortie de l’analyseur.",
        "apihelp-parse-param-disabletidy": "Ne pas exécuter de nettoyage du code HTML (par exemple,  réagencer) sur la sortie de l'analyseur.",
        "apihelp-query+embeddedin-param-limit": "Combien de pages renvoyer au total.",
        "apihelp-query+embeddedin-example-simple": "Afficher les pages incluant <kbd>Template:Stub</kbd>.",
        "apihelp-query+embeddedin-example-generator": "Obtenir des informations sur les pages incluant <kbd>Template:Stub</kbd>.",
-       "apihelp-query+extlinks-summary": "Renvoyer toutes les URLs externes (non interwikis) des pages données.",
+       "apihelp-query+extlinks-summary": "Renvoyer toutes les URL externes (non interwikis) des pages données.",
        "apihelp-query+extlinks-param-limit": "Combien de liens renvoyer.",
        "apihelp-query+extlinks-param-protocol": "Protocole de l’URL. Si vide et <var>$1query</var> est positionné, le protocole est <kbd>http</kbd>. Laisser à la fois ceci et <var>$1query</var> vides pour lister tous les liens externes.",
        "apihelp-query+extlinks-param-query": "Rechercher une chaîne sans protocole. Utile pour vérifier si une certaine page contient une certaine URL externe.",
-       "apihelp-query+extlinks-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
+       "apihelp-query+extlinks-param-expandurl": "Étendre les URL relatives au protocole avec le protocole canonique.",
        "apihelp-query+extlinks-example-simple": "Obtenir une liste des liens externes de <kbd>Main Page</kbd>.",
        "apihelp-query+exturlusage-summary": "Énumérer les pages contenant une URL donnée.",
        "apihelp-query+exturlusage-param-prop": "Quelles informations inclure :",
        "apihelp-query+exturlusage-param-query": "Rechercher une chaîne sans protocole. Voyez [[Special:LinkSearch]]. Le laisser vide pour lister tous les liens externes.",
        "apihelp-query+exturlusage-param-namespace": "Les espaces de nom à énumérer.",
        "apihelp-query+exturlusage-param-limit": "Combien de pages renvoyer.",
-       "apihelp-query+exturlusage-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
+       "apihelp-query+exturlusage-param-expandurl": "Étendre les URL relatives au protocole avec le protocole canonique.",
        "apihelp-query+exturlusage-example-simple": "Afficher les pages avec un lien vers <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Énumérer séquentiellement tous les fichiers supprimés.",
        "apihelp-query+filearchive-param-from": "Le titre de l’image auquel démarrer l’énumération.",
        "apihelp-query+filearchive-param-limit": "Combien d’images renvoyer au total.",
        "apihelp-query+filearchive-param-dir": "La direction dans laquelle lister.",
        "apihelp-query+filearchive-param-sha1": "Hachage SHA1 de l’image. Écrase $1sha1base36.",
-       "apihelp-query+filearchive-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MédiaWiki).",
+       "apihelp-query+filearchive-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MediaWiki).",
        "apihelp-query+filearchive-param-prop": "Quelle information obtenir sur l’image :",
        "apihelp-query+filearchive-paramvalue-prop-sha1": "Ajoute le hachage SHA-1 pour l’image.",
        "apihelp-query+filearchive-paramvalue-prop-timestamp": "Ajoute l’horodatage à la version téléversée.",
        "apihelp-query+filerepoinfo-paramvalue-prop-local": "Si ce dépôt est local ou non.",
        "apihelp-query+filerepoinfo-paramvalue-prop-name": "La clé du dépôt — utilisée dans les valeurs de retour, par ex. <var>[[mw:Special:MyLanguage/Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var> et [[Special:ApiHelp/query+imageinfo|imageinfo]] return values.",
        "apihelp-query+filerepoinfo-paramvalue-prop-rootUrl": "Chemin de l’URL racine pour les chemins d’image.",
-       "apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "Chemin de l’URL racine pour l’installation de MédiaWiki du wiki du dépôt.",
+       "apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "Chemin de l’URL racine pour l’installation de MediaWiki du wiki du dépôt.",
        "apihelp-query+filerepoinfo-paramvalue-prop-server": "<var>[[mw:Special:MyLanguage/Manual:$wgServer|$wgServer]]</var> du wiki du dépôt, ou équivalent.",
        "apihelp-query+filerepoinfo-paramvalue-prop-thumbUrl": "Chemin de l’URL racine pour les chemins des vignettes.",
        "apihelp-query+filerepoinfo-paramvalue-prop-url": "Chemin de l’URL de la zone publique.",
        "apihelp-query+imageinfo-paramvalue-prop-extmetadata": "Liste les métadonnées mises en forme combinées depuis diverses sources. Les résultats sont au format HTML.",
        "apihelp-query+imageinfo-paramvalue-prop-archivename": "Ajoute le nom de fichier de la version d’archive pour les versions autres que la dernière.",
        "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "Ajoute la profondeur de bits de la version.",
-       "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Utilisé par la page Special:Upload pour obtenir de l’information sur un fichier existant. Non prévu pour être utilisé en dehors du cœur de MédiaWiki.",
+       "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Utilisé par la page Special:Upload pour obtenir de l’information sur un fichier existant. Non prévu pour être utilisé en dehors du cœur de MediaWiki.",
        "apihelp-query+imageinfo-paramvalue-prop-badfile": "Ajoute l'indication que le fichier est sur [[MediaWiki:Bad image list]]",
        "apihelp-query+imageinfo-param-limit": "Combien de révisions de fichier renvoyer par fichier.",
        "apihelp-query+imageinfo-param-start": "Horodatage auquel démarrer la liste.",
        "apihelp-query+info-example-simple": "Obtenir des informations sur la page <kbd>Main Page</kbd>.",
        "apihelp-query+info-example-protection": "Obtenir des informations générales et de protection sur la page <kbd>Main Page</kbd>.",
        "apihelp-query+iwbacklinks-summary": "Trouver toutes les pages qui ont un lien vers le lien interwiki indiqué.",
-       "apihelp-query+iwbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un préfixe, ou tous les liens vers un titre (avec un préfixe donné). Sans paramètre, équivaut à « tous les liens interwiki ».",
+       "apihelp-query+iwbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un préfixe, ou tous les liens vers un titre (avec un préfixe donné). Sans paramètre, équivaut à « tous les liens interwiki ».",
        "apihelp-query+iwbacklinks-param-prefix": "Préfixe pour l’interwiki.",
        "apihelp-query+iwbacklinks-param-title": "Lien interwiki à rechercher. Doit être utilisé avec <var>$1blprefix</var>.",
        "apihelp-query+iwbacklinks-param-limit": "Combien de pages renvoyer.",
        "apihelp-query+iwlinks-param-dir": "La direction dans laquelle lister.",
        "apihelp-query+iwlinks-example-simple": "Obtenir les liens interwiki de la page <kbd>Main Page</kbd>.",
        "apihelp-query+langbacklinks-summary": "Trouver toutes les pages qui ont un lien vers le lien de langue indiqué.",
-       "apihelp-query+langbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un code de langue, ou tous les liens vers un titre (avec une langue donnée). Sans paramètre équivaut à « tous les liens de langue ».\n\nNotez que cela peut ne pas prendre en compte les liens de langue ajoutés par les extensions.",
+       "apihelp-query+langbacklinks-extended-description": "Peut être utilisé pour trouver tous les liens avec un code de langue, ou tous les liens vers un titre (avec une langue donnée). Sans paramètre équivaut à « tous les liens de langue ».\n\nNotez que cela peut ne pas prendre en compte les liens de langue ajoutés par les extensions.",
        "apihelp-query+langbacklinks-param-lang": "Langue pour le lien de langue.",
        "apihelp-query+langbacklinks-param-title": "Lien interlangue à rechercher. Doit être utilisé avec $1lang.",
        "apihelp-query+langbacklinks-param-limit": "Combien de pages renvoyer au total.",
        "apihelp-query+languageinfo-summary": "Renvoyer des informations sur les langues disponibles.",
        "apihelp-query+languageinfo-extended-description": "[[mw:API:Query#Continuing queries|Un prolongement]] peut être appliqué si la récupération de l’information prend trop de temps pour une requête.",
        "apihelp-query+languageinfo-param-prop": "Quelle information obtenir pour chaque langue.",
-       "apihelp-query+languageinfo-paramvalue-prop-code": "Le code de langue (ce code est spécifique à MédiaWiki, bien qu’il y ait des recouvrements avec d’autres standards).",
+       "apihelp-query+languageinfo-paramvalue-prop-code": "Le code de langue (ce code est spécifique à MediaWiki, bien qu’il y ait des recouvrements avec d’autres standards).",
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "Le code de langue BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "La direction d’écriture de la langue (<code>ltr</code> ou <code>rtl</code>).",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme d’une langue, c’est-à-dire son nom dans cette langue.",
        "apihelp-query+siteinfo-paramvalue-prop-fileextensions": "Renvoie la liste des extensions de fichiers (types de fichiers) autorisées au téléversement.",
        "apihelp-query+siteinfo-paramvalue-prop-rightsinfo": "Renvoie l’information sur les droits du wiki (sa licence), si elle est disponible.",
        "apihelp-query+siteinfo-paramvalue-prop-restrictions": "Renvoie l’information sur les types de restriction disponibles (protection).",
-       "apihelp-query+siteinfo-paramvalue-prop-languages": "Renvoie une liste des langues que MédiaWiki prend en charge (éventuellement localisée en utilisant <var>$1inlanguagecode</var>).",
+       "apihelp-query+siteinfo-paramvalue-prop-languages": "Renvoie une liste des langues que MediaWiki prend en charge (éventuellement localisée en utilisant <var>$1inlanguagecode</var>).",
        "apihelp-query+siteinfo-paramvalue-prop-languagevariants": "Renvoie une liste de codes de langue pour lesquels [[mw:Special:MyLanguage/LanguageConverter|LanguageConverter]] est activé, et les variantes prises en charge pour chacun.",
        "apihelp-query+siteinfo-paramvalue-prop-skins": "Renvoie une liste de tous les habillages activés (éventuellement localisé en utilisant <var>$1inlanguagecode</var>, sinon dans la langue du contenu).",
        "apihelp-query+siteinfo-paramvalue-prop-extensiontags": "Renvoie une liste des balises d’extension de l’analyseur.",
        "apihelp-query+users-paramvalue-prop-editcount": "Ajoute le compteur de modifications de l’utilisateur.",
        "apihelp-query+users-paramvalue-prop-registration": "Ajoute l’horodatage d’inscription de l’utilisateur.",
        "apihelp-query+users-paramvalue-prop-emailable": "Marque si l’utilisateur peut et veut recevoir des courriels via [[Special:Emailuser]].",
-       "apihelp-query+users-paramvalue-prop-gender": "Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
+       "apihelp-query+users-paramvalue-prop-gender": "Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
        "apihelp-query+users-paramvalue-prop-centralids": "Ajoute les IDs centraux et l’état d’attachement de l’utilisateur.",
        "apihelp-query+users-paramvalue-prop-cancreate": "Indique si un compte peut être créé pour les noms d’utilisateurs valides mais non enregistrés.",
        "apihelp-query+users-param-attachedwiki": "Avec <kbd>$1prop=centralids</kbd>, indiquer si l’utilisateur est attaché au wiki identifié par cet ID.",
        "apihelp-rsd-summary": "Exporter un schéma RSD (Découverte Très Simple).",
        "apihelp-rsd-example-simple": "Exporter le schéma RSD",
        "apihelp-setnotificationtimestamp-summary": "Mettre à jour l’horodatage de notification pour les pages suivies.",
-       "apihelp-setnotificationtimestamp-extended-description": "Cela affecte la mise en évidence des pages modifiées dans la liste de suivi et l’historique, et l’envoi de courriel quand la préférence « {{int:tog-enotifwatchlistpages}} » est activée.",
+       "apihelp-setnotificationtimestamp-extended-description": "Cela affecte la mise en évidence des pages modifiées dans la liste de suivi et l’historique, et l’envoi de courriel quand la préférence « {{int:tog-enotifwatchlistpages}} » est activée.",
        "apihelp-setnotificationtimestamp-param-entirewatchlist": "Travailler sur toutes les pages suivies.",
        "apihelp-setnotificationtimestamp-param-timestamp": "Horodatage auquel dater la notification.",
        "apihelp-setnotificationtimestamp-param-torevid": "Révision pour laquelle fixer l’horodatage de notification (une page uniquement).",
        "apihelp-unlinkaccount-summary": "Supprimer un compte tiers lié de l’utilisateur actuel.",
        "apihelp-unlinkaccount-example-simple": "Essayer de supprimer le lien de l’utilisateur actuel pour le fournisseur associé avec <kbd>FooAuthenticationRequest</kbd>.",
        "apihelp-upload-summary": "Téléverser un fichier, ou obtenir l’état des téléversements en cours.",
-       "apihelp-upload-extended-description": "Plusieurs méthodes sont disponibles :\n* Téléverser directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Téléverser le fichier par morceaux, en utilisant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.\n* Pour que le serveur MédiaWiki cherche un fichier depuis une URL, utilisez le paramètre <var>$1url</var>.\n* Terminer un téléversement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléversement de fichier (par exemple en utilisant <code>multipart/form-data</code>) en envoyant le <var>$1file</var>.",
+       "apihelp-upload-extended-description": "Plusieurs méthodes sont disponibles :\n* Téléverser directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Téléverser le fichier par morceaux, en utilisant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.\n* Pour que le serveur MediaWiki cherche un fichier depuis une URL, utilisez le paramètre <var>$1url</var>.\n* Terminer un téléversement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléversement de fichier (par exemple en utilisant <code>multipart/form-data</code>) en envoyant le <var>$1file</var>.",
        "apihelp-upload-param-filename": "Nom de fichier cible.",
        "apihelp-upload-param-comment": "Téléverser le commentaire. Utilisé aussi comme texte de la page initiale pour les nouveaux fichiers si <var>$1text</var> n’est pas spécifié.",
        "apihelp-upload-param-tags": "Modifier les balises à appliquer à l’entrée du journal de téléversement et à la révision de la page du fichier.",
        "api-pageset-param-titles": "Une liste des titres sur lesquels travailler.",
        "api-pageset-param-pageids": "Une liste des IDs de pages sur lesquelles travailler.",
        "api-pageset-param-revids": "Une liste des IDs de révisions sur lesquelles travailler.",
-       "api-pageset-param-generator": "Obtenir la liste des pages sur lesquelles travailler en exécutant le module de requête spécifié.\n\n<strong>NOTE :<strong> les noms de paramètre du générateur doivent être préfixés avec un « g », voir les exemples.",
+       "api-pageset-param-generator": "Obtenir la liste des pages sur lesquelles travailler en exécutant le module de requête spécifié.\n\n<strong>NOTE :<strong> les noms de paramètre du générateur doivent être préfixés avec un « g », voir les exemples.",
        "api-pageset-param-redirects-generator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>, et dans les pages renvoyées par <var>$1generator</var>.",
        "api-pageset-param-redirects-nogenerator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>.",
        "api-pageset-param-converttitles": "Convertir les titres dans d’autres variantes si nécessaire. Fonctionne uniquement si la langue de contenu du wiki prend en charge la conversion en variantes. Les langues qui prennent en charge la conversion en variantes incluent $1.",
        "api-help-param-templated-var-first": "<var>&#x7B;$1&#x7D;</var> dans le nom du paramètre doit être remplacé par des valeurs de <var>$2</var>",
        "api-help-param-templated-var": "<var>&#x7B;$1&#x7D;</var> par les valeurs de <var>$2</var>",
        "api-help-datatypes-header": "Type de données",
-       "api-help-datatypes": "Les entrées dans MédiaWiki doivent être en UTF-8 à la norme NFC. MédiaWiki peut tenter de convertir d’autres types d’entrées, mais cela peut faire échouer certaines opérations (comme les [[Special:ApiHelp/edit|modifications]] avec contrôles MD5).\n\nCertains types de paramètres dans les requêtes de l’API nécessitent plus d’explication&nbsp;:\n;boolean\n:Les paramètres booléens fonctionnent comme des cases à cocher HTML&nbsp;: si le paramètre est spécifié, quelle que soit sa valeur, il est considéré comme vrai. Pour une valeur fausse, enlever complètement le paramètre.\n;timestamp\n:Les horodatages peuvent être spécifiés sous différentes formes, voir [[mw:Special:MyLanguage/Timestamp|les formats d’entrées de la librairie Timestampdocumentés sur mediawiki.org]] pour plus de détails. La date et heure ISO 8601 est recommandée&nbsp;: <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd>. De plus, la chaîne de caractères <kbd>now</kbd> peut être utilisée pour spécifier le fuseau horaire actuel.\n;séparateur multi-valeurs alternatif\n:Les paramètres prenant plusieurs valeurs sont normalement validés lorsque celles-ci sont séparées par le caractère «&nbsp;pipe&nbsp;» (|), ex. <kbd>paramètre=valeur1|valeur2</kbd> ou <kbd>paramètre=valeur1%7Cvaleur2</kbd>. Si une valeur doit contenir le caractère «&nbsp;pipe&nbsp;», utiliser U+001F (séparateur de sous-articles) comme séparateur ''et'' la préfixer de U+001F, ex. <kbd>paramètre=%1Fvaleur1%1Fvaleur2</kbd>.",
+       "api-help-datatypes": "Les entrées dans MediaWiki doivent être en UTF-8 à la norme NFC. MediaWiki peut tenter de convertir d’autres types d’entrées, mais cela peut faire échouer certaines opérations (comme les [[Special:ApiHelp/edit|modifications]] avec contrôles MD5).\n\nCertains types de paramètres dans les requêtes de l’API nécessitent plus d’explication&nbsp;:\n;boolean\n:Les paramètres booléens fonctionnent comme des cases à cocher HTML&nbsp;: si le paramètre est spécifié, quelle que soit sa valeur, il est considéré comme vrai. Pour une valeur fausse, enlever complètement le paramètre.\n;timestamp\n:Les horodatages peuvent être spécifiés sous différentes formes, voir [[mw:Special:MyLanguage/Timestamp|les formats d’entrées de la librairie Timestampdocumentés sur mediawiki.org]] pour plus de détails. La date et heure ISO 8601 est recommandée&nbsp;: <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd>. De plus, la chaîne de caractères <kbd>now</kbd> peut être utilisée pour spécifier le fuseau horaire actuel.\n;séparateur multi-valeurs alternatif\n:Les paramètres prenant plusieurs valeurs sont normalement validés lorsque celles-ci sont séparées par le caractère «&nbsp;pipe&nbsp;» (|), ex. <kbd>paramètre=valeur1|valeur2</kbd> ou <kbd>paramètre=valeur1%7Cvaleur2</kbd>. Si une valeur doit contenir le caractère «&nbsp;pipe&nbsp;», utiliser U+001F (séparateur de sous-articles) comme séparateur ''et'' la préfixer de U+001F, ex. <kbd>paramètre=%1Fvaleur1%1Fvaleur2</kbd>.",
        "api-help-templatedparams-header": "Paramètres de modèle",
        "api-help-templatedparams": "Les paramètres de modèle supportent les cas où un module d’API a besoin d’une valeur pour chaque valeur d’un autre paramètre quelconque. Par exemple, s’il y avait un module d’API pour demander un fruit, il pourrait avoir un paramètre <var>fruits</var> pour spécifier quels fruits sont demandés et un paramètre de modèle <var>{fruit}-quantité</var> pour spécifier la quantité demandée de chaque fruit. Un client de l’API qui voudrait une pomme, cinq bananes et vingt fraises pourrait alors faire une requête comme <kbd>fruits=pommes|bananes|fraises&pommes-quantité=1&bananes-quantité=5&fraises-quantité=20</kbd>.",
        "api-help-param-type-limit": "Type : entier ou <kbd>max</kbd>",
        "api-help-param-multi-all": "Pour spécifier toutes les valeurs, utiliser <kbd>$1</kbd>.",
        "api-help-param-default": "Par défaut : $1",
        "api-help-param-default-empty": "Par défaut : <span class=\"apihelp-empty\">(vide)</span>",
-       "api-help-param-token": "Un jeton « $1 » récupéré par [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+       "api-help-param-token": "Un jeton « $1 » récupéré par [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
        "api-help-param-token-webui": "Pour rester compatible, le jeton utilisé dans l’IHM web est aussi accepté.",
        "api-help-param-disabled-in-miser-mode": "Désactivé à cause du [[mw:Special:MyLanguage/Manual:$wgMiserMode|mode minimal]].",
        "api-help-param-limited-in-miser-mode": "<strong>NOTE :</strong> Du fait du [[mw:Special:MyLanguage/Manual:$wgMiserMode|mode minimal]], utiliser cela peut aboutir à moins de résultats que <var>$1limit</var> renvoyés avant de continuer ; dans les cas extrêmes, zéro résultat peut être renvoyé.",
        "apierror-appendnotsupported": "Impossible d’ajouter aux pages utilisant le modèle de contenu $1.",
        "apierror-articleexists": "L’article que vous essayez de créer l’a déjà été.",
        "apierror-assertbotfailed": "La vérification que l’utilisateur a le droit <code>bot</code> a échoué.",
-       "apierror-assertnameduserfailed": "La vérification que l’utilisateur est « $1 » a échoué.",
+       "apierror-assertnameduserfailed": "La vérification que l’utilisateur est « $1 » a échoué.",
        "apierror-assertuserfailed": "La vérification que l’utilisateur est connecté a échoué.",
        "apierror-autoblocked": "Votre adresse IP a été bloquée automatiquement, parce qu’elle a été utilisée par un utilisateur bloqué.",
        "apierror-bad-badfilecontexttitle": "Titre invalide dans le paramètre <var>$1badfilecontexttitle</var> .",
        "apierror-badgenerator-unknown": "<kbd>generator=$1</kbd> inconnu.",
        "apierror-badip": "Paramètre IP non valide.",
        "apierror-badmd5": "Le hachage MD5 fourni n’était pas correct.",
-       "apierror-badmodule-badsubmodule": "Le module <kbd>$1</kbd> n’a pas de sous-module « $2 ».",
+       "apierror-badmodule-badsubmodule": "Le module <kbd>$1</kbd> n’a pas de sous-module « $2 ».",
        "apierror-badmodule-nosubmodules": "Le module <kbd>$1</kbd> n’a pas de sous-modules.",
        "apierror-badparameter": "Valeur non valide pour le paramètre <var>$1</var>.",
        "apierror-badquery": "Requête invalide.",
-       "apierror-badtimestamp": "Valeur non valide « $2 » pour le paramètre de référence horaire  <var>$1</var>.",
+       "apierror-badtimestamp": "Valeur non valide « $2 » pour le paramètre de référence horaire  <var>$1</var>.",
        "apierror-badtoken": "Jeton CSRF non valide.",
        "apierror-badupload": "Le paramètre de téléversement de fichier <var>$1</var> n’est pas un téléversement de fichier ; assurez-vous d’utiliser <code>multipart/form-data</code> pour votre POST et d’inclure un nom de fichier dans l’entête <code>Content-Disposition</code>.",
-       "apierror-badurl": "Valeur « $2 » non valide pour le paramètre d’URL <var>$1</var>.",
-       "apierror-baduser": "Valeur « $2 » non valide pour le paramètre utilisateur <var>$1</var>.",
+       "apierror-badurl": "Valeur « $2 » non valide pour le paramètre d’URL <var>$1</var>.",
+       "apierror-baduser": "Valeur « $2 » non valide pour le paramètre utilisateur <var>$1</var>.",
        "apierror-badvalue-notmultivalue": "La séparation multi-valeur U+001F ne peut être utilisée que pour des paramètres multi-valeurs.",
        "apierror-bad-watchlist-token": "Jeton de liste de suivi fourni non valide. Veuillez mettre un jeton valide dans [[Special:Preferences]].",
        "apierror-blockedfrommail": "Vous avez été bloqué pour l’envoi de courriel.",
        "apierror-invalidsection": "Le paramètre <var>section</var> doit être un ID de section valide ou <kbd>new</kbd>.",
        "apierror-invalidsha1base36hash": "Le hachage SHA1Base36 fourni n’est pas valide.",
        "apierror-invalidsha1hash": "Le hachage SHA1 fourni n’est pas valide.",
-       "apierror-invalidtitle": "Mauvais titre « $1 ».",
+       "apierror-invalidtitle": "Mauvais titre « $1 ».",
        "apierror-invalidurlparam": "Valeur non valide pour <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
        "apierror-invaliduser": "Nom d'utilisateur invalide \"$1\".",
        "apierror-invaliduserid": "L'ID d'utilisateur <var>$1</var> n'est pas valide.",
        "apierror-paramempty": "Le paramètre <var>$1</var> ne peut pas être vide.",
        "apierror-parsetree-notwikitext": "<kbd>prop=parsetree</kbd> n’est pris en charge que pour le contenu wikitexte.",
        "apierror-parsetree-notwikitext-title": "<kbd>prop=parsetree</kbd> n’est pris en charge que pour le contenu wikitexte. $1 utilise le modèle de contenu $2.",
-       "apierror-pastexpiry": "La date d’expiration « $1 » est dépassée.",
+       "apierror-pastexpiry": "La date d’expiration « $1 » est dépassée.",
        "apierror-permissiondenied": "Vous n’avez pas le droit de $1.",
        "apierror-permissiondenied-generic": "Autorisation refusée.",
        "apierror-permissiondenied-patrolflag": "Vous avez besoin du droit <code>patrol</code> ou <code>patrolmarks</code> pour demander le drapeau patrouillé.",
        "apierror-permissiondenied-unblock": "Vous n’avez pas le droit de débloquer les utilisateurs.",
        "apierror-prefixsearchdisabled": "La recherche de préfixe est désactivée en mode misérable.",
        "apierror-promised-nonwrite-api": "L’entête HTTP <code>Promise-Non-Write-API-Action</code> ne peut pas être envoyé aux modules de l’API en mode écriture.",
-       "apierror-protect-invalidaction": "Type de protection non valide « $1 ».",
-       "apierror-protect-invalidlevel": "Niveau de protection non valide « $1 ».",
+       "apierror-protect-invalidaction": "Type de protection non valide « $1 ».",
+       "apierror-protect-invalidlevel": "Niveau de protection non valide « $1 ».",
        "apierror-ratelimited": "Vous avez dépassé votre limite de débit. Veuillez attendre un peu et réessayer.",
        "apierror-readapidenied": "Vous avez besoin du droit de lecture pour utiliser ce module.",
        "apierror-readonly": "Ce wiki est actuellement en mode lecture seule.",
        "apiwarn-deprecation-login-token": "La récupération d’un jeton via <kbd>action=login</kbd> est désuète. Utilisez <kbd>action=query&meta=tokens&type=login</kbd> à la place.",
        "apiwarn-deprecation-missingparam": "Comme <var>$1</var> n’a pas été spécifié, un format ancien a été utilisé pour la sortie. Ce format est obsolète, et dans le futur, le nouveau format sera toujours utilisé.",
        "apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est désuet.",
-       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+       "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MediaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
        "apiwarn-deprecation-post-without-content-type": "Une requête POST a été faite sans entête <code>Content-Type</code>. Cela ne fonctionne pas de façon fiable.",
        "apiwarn-deprecation-purge-get": "L’utilisation de <kbd>action=purge</kbd> via un GET est désuète. Utiliser POST à la place.",
        "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
        "apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
        "apiwarn-errorprinterfailed": "Erreur échec imprimante. Nouvel essai sans paramètres.",
        "apiwarn-ignoring-invalid-templated-value": "Ignorer la valeur <kbd>$2</kbd> dans <var>$1</var> en traitant les paramètres de modèle.",
-       "apiwarn-invalidcategory": "« $1 » n'est pas une catégorie.",
-       "apiwarn-invalidtitle": "« $1 » n’est pas un titre valide.",
+       "apiwarn-invalidcategory": "« $1 » n'est pas une catégorie.",
+       "apiwarn-invalidtitle": "« $1 » n’est pas un titre valide.",
        "apiwarn-invalidxmlstylesheetext": "Une feuille de style doit avoir une extension <code>.xsl</code>.",
        "apiwarn-invalidxmlstylesheet": "Feuille de style spécifiée non valide ou inexistante.",
        "apiwarn-invalidxmlstylesheetns": "La feuille de style devrait être dans l’espace de noms {{ns:MediaWiki}}.",
        "apiwarn-moduleswithoutvars": "La propriété <kbd>modules</kbd> a été définie mais pas <kbd>jsconfigvars</kbd> ni <kbd>encodedjsconfigvars</kbd>. Les variables de configuration sont nécessaires pour une utilisation correcte du module.",
-       "apiwarn-notfile": "« $1 » n'est pas un fichier.",
+       "apiwarn-notfile": "« $1 » n'est pas un fichier.",
        "apiwarn-nothumb-noimagehandler": "Impossible de créer la vignette car $1 n’a pas de gestionnaire d’image associé.",
        "apiwarn-parse-nocontentmodel": "Ni <var>title</var> ni <var>contentmodel</var> n’ont été fournis, $1 est supposé.",
        "apiwarn-parse-revidwithouttext": "<var>revid</var> utilisé sans <var>text</var>, et les propriétés de la page analysée ont été demandées. Vouliez-vous utiliser <var>oldid</var> au lieu de <var>revid</var> ?",
        "apiwarn-parse-titlewithouttext": "<var>title</var> utilisé sans <var>text</var>, et les propriétés de page analysées sont nécessaires. Voulez-vous dire que vous voulez utiliser <var>page</var> à la place de <var>title</var> ?",
        "apiwarn-redirectsandrevids": "La résolution de la redirection ne peut pas être utilisée avec le paramètre <var>revids</var>. Toutes les redirections vers lesquelles pointent <var>revids</var> n’ont pas été résolues.",
-       "apiwarn-tokennotallowed": "L'action « $1 » n'est pas autorisée pour l'utilisateur actuel.",
+       "apiwarn-tokennotallowed": "L'action « $1 » n'est pas autorisée pour l'utilisateur actuel.",
        "apiwarn-tokens-origin": "Les jetons ne peuvent pas être obtenus quand la politique de même origine n’est pas appliquée.",
        "apiwarn-truncatedresult": "Ce résultat a été tronqué parce que sinon, il dépasserait la limite de $1 octets.",
-       "apiwarn-unclearnowtimestamp": "Passer « $2 » comme paramètre d’horodatage <var>$1</var> est obsolète. Si, pour une raison quelconque, vous avez besoin de spécifier explicitement l’heure courante sans la recalculer du côté client, utilisez <kbd>now</kbd>.",
+       "apiwarn-unclearnowtimestamp": "Passer « $2 » comme paramètre d’horodatage <var>$1</var> est obsolète. Si, pour une raison quelconque, vous avez besoin de spécifier explicitement l’heure courante sans la recalculer du côté client, utilisez <kbd>now</kbd>.",
        "apiwarn-unrecognizedvalues": "{{PLURAL:$3|Valeur non reconnue|Valeurs non reconnues}} pour le paramètre <var>$1</var> : $2.",
        "apiwarn-unsupportedarray": "Le paramètre <var>$1</var> utilise une syntaxe PHP de tableau non prise en charge.",
        "apiwarn-urlparamwidth": "Valeur de la largeur définie dans <var>$1urlparam</var> ($2) ignorée en faveur de la largeur calculée à partir de <var>$1urlwidth</var>/<var>$1urlheight</var> ($3).",
index c91976f..4d02204 100644 (file)
@@ -37,7 +37,8 @@
                        "Edward Chernenko",
                        "Vlad5250",
                        "Diralik",
-                       "DmitTrix"
+                       "DmitTrix",
+                       "Марио"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Документация]]\n* [[mw:Special:MyLanguage/API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n</div>\n<strong>Статус:</strong> MediaWiki API — зрелый и стабильный интерфейс, активно поддерживаемый и улучшаемый. Мы стараемся избегать ломающих изменений, однако изредка они могут быть необходимы. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом «MediaWiki-API-Error», после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Ошибки и предупреждения]].\n\n<p class=\"mw-apisandbox-link\"><strong>Тестирование:</strong> для удобства тестирования API-запросов, см. [[Special:ApiSandbox]].</p>",
@@ -94,6 +95,7 @@
        "apihelp-compare-param-fromid": "Идентификатор первой сравниваемой страницы.",
        "apihelp-compare-param-fromrev": "Первая сравниваемая версия.",
        "apihelp-compare-param-frompst": "Выполнить преобразование перед записью правки (PST) над <var>fromtext-&#x7B;slot}</var>.",
+       "apihelp-compare-param-fromslots": "Переопределение содержимого версии, заданной параметром <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>.\n\nЭтот параметр определяет слоты, которые должны быть изменены. Используйте <var>fromtext-&#x7B;slot}</var>, <var>fromcontentmodel-&#x7B;slot}</var>, и <var>fromcontentformat-&#x7B;slot}</var> для определения содержимого для каждого слота.",
        "apihelp-compare-param-fromtext": "Укажите <kbd>fromslots=main</kbd> и используйте <var>fromtext-main</var>.",
        "apihelp-compare-param-fromcontentmodel": "Укажите <kbd>fromslots=main</kbd> и используйте <var>fromcontentmodel-main</var>.",
        "apihelp-compare-param-fromcontentformat": "Укажите <kbd>fromslots=main</kbd> и используйте <var>fromcontentformat-main</var>.",
        "apihelp-query+blocks-paramvalue-prop-reason": "Добавляет причину блокировки.",
        "apihelp-query+blocks-paramvalue-prop-range": "Добавляет диапазон IP-адресов, затронутых блокировкой.",
        "apihelp-query+blocks-paramvalue-prop-flags": "Добавляет бану метку (autoblock, anonoly, и так далее).",
+       "apihelp-query+blocks-paramvalue-prop-restrictions": "Добавляет ограничения частичных блокировок, если блокировка не действует во всём проекте.",
        "apihelp-query+blocks-param-show": "Показать только элементы, удовлетворяющие этим критериям.\nНапример, чтобы отобразить только бессрочные блокировки IP-адресов, установите <kbd>$1show=ip|!temp</kbd>.",
        "apihelp-query+blocks-example-simple": "Список блокировок.",
        "apihelp-query+blocks-example-users": "Список блокировок участников <kbd>Alice</kbd> и <kbd>Bob</kbd>.",
        "apierror-bad-watchlist-token": "Предоставлен некорректный токен списка наблюдения. Пожалуйста, установите корректный токен в [[Special:Preferences]].",
        "apierror-blockedfrommail": "Отправка электронной почты была для вас заблокирована.",
        "apierror-blocked": "Редактирование было для вас заблокировано.",
+       "apierror-blocked-partial": "Вы были заблокированы от редактирования этой страницы.",
        "apierror-botsnotsupported": "Этот интерфейс не поддерживается для ботов.",
        "apierror-cannot-async-upload-file": "Параметры <var>async</var> и <var>file</var> не могут применяться вместе. Если вы хотите ассинхронно обработать загруженный файл, сначала загрузите его во временное хранилище (используя параметр <var>stash</var>), а затем опубликуйте этот файл ассинхронно (используя параметры <var>filekey</var> и <var>async</var>).",
        "apierror-cannotreauthenticate": "Это действие недоступно, так как ваша личность не может быть подтверждена.",
index e7527d1..bc70d5e 100644 (file)
@@ -247,12 +247,18 @@ abstract class AuthenticationRequest {
 
        /**
         * Select a request by class name.
+        *
+        * @codingStandardsIgnoreStart
+        * @phan-template T
+        * @codingStandardsIgnoreEnd
         * @param AuthenticationRequest[] $reqs
         * @param string $class Class name
+        * @phan-param class-string<T> $class
         * @param bool $allowSubclasses If true, also returns any request that's a subclass of the given
         *   class.
         * @return AuthenticationRequest|null Returns null if there is not exactly
         *  one matching request.
+        * @phan-return T|null
         */
        public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
                $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
index c831fc8..25a1754 100644 (file)
@@ -96,7 +96,9 @@ class ResetPasswordSecondaryAuthenticationProvider extends AbstractSecondaryAuth
                        }
                }
 
+               /** @var PasswordAuthenticationRequest $needReq */
                $needReq = $data->req ?? new PasswordAuthenticationRequest();
+               '@phan-var PasswordAuthenticationRequest $needReq';
                if ( !$needReq->action ) {
                        $needReq->action = AuthManager::ACTION_CHANGE;
                }
index 6007abd..b1a8e21 100644 (file)
@@ -24,7 +24,6 @@ namespace MediaWiki\Block;
 
 use ActorMigration;
 use AutoCommitUpdate;
-use BadMethodCallException;
 use CommentStore;
 use DeferredUpdates;
 use Hooks;
@@ -161,47 +160,6 @@ class DatabaseBlock extends AbstractBlock {
                }
        }
 
-       /**
-        * Return the list of ipblocks fields that should be selected to create
-        * a new block.
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return array
-        */
-       public static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->ipb_by or $row->ipb_by_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               wfDeprecated( __METHOD__, '1.31' );
-               return [
-                       'ipb_id',
-                       'ipb_address',
-                       'ipb_by',
-                       'ipb_by_text',
-                       'ipb_by_actor' => 'NULL',
-                       'ipb_timestamp',
-                       'ipb_auto',
-                       'ipb_anon_only',
-                       'ipb_create_account',
-                       'ipb_enable_autoblock',
-                       'ipb_expiry',
-                       'ipb_deleted',
-                       'ipb_block_email',
-                       'ipb_allow_usertalk',
-                       'ipb_parent_block_id',
-                       'ipb_sitewide',
-               ] + CommentStore::getStore()->getFields( 'ipb_reason' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new block object.
index a0d61b2..ab78ee4 100644 (file)
@@ -94,10 +94,6 @@ class HTMLFileCache extends FileCacheBase {
                $config = MediaWikiServices::getInstance()->getMainConfig();
 
                if ( !$config->get( 'UseFileCache' ) && $mode !== self::MODE_REBUILD ) {
-                       return false;
-               } elseif ( $config->get( 'DebugToolbar' ) ) {
-                       wfDebug( "HTML file cache skipped. \$wgDebugToolbar on\n" );
-
                        return false;
                }
 
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 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 696bbf4..bd174b2 100644 (file)
@@ -66,7 +66,8 @@ class ConfigFactory implements SalvageableService {
        public function salvage( SalvageableService $other ) {
                Assert::parameterType( self::class, $other, '$other' );
 
-               /** @var ConfigFactory $other */
+               /** @var self $other */
+               '@phan-var self $other';
                foreach ( $other->factoryFunctions as $name => $otherFunc ) {
                        if ( !isset( $this->factoryFunctions[$name] ) ) {
                                continue;
index d48eb0e..ceb3944 100644 (file)
@@ -188,6 +188,8 @@ class ConfigRepository implements SalvageableService {
         */
        public function salvage( SalvageableService $other ) {
                Assert::parameterType( self::class, $other, '$other' );
+               /** @var self $other */
+               '@phan-var self $other';
 
                foreach ( $other->configItems['public'] as $name => $otherConfig ) {
                        if ( isset( $this->configItems['public'][$name] ) ) {
index e6a856c..cbcaba1 100644 (file)
@@ -81,6 +81,12 @@ class RequestContext implements IContextSource, MutableContext {
         */
        private static $instance = null;
 
+       /**
+        * Boolean flag to guard against recursion in getLanguage
+        * @var bool
+        */
+       private $languageRecursion = false;
+
        /**
         * @param Config $config
         */
@@ -318,7 +324,7 @@ class RequestContext implements IContextSource, MutableContext {
         * @since 1.19
         */
        public function getLanguage() {
-               if ( isset( $this->recursion ) ) {
+               if ( $this->languageRecursion === true ) {
                        trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING );
                        $e = new Exception;
                        wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
@@ -326,7 +332,7 @@ class RequestContext implements IContextSource, MutableContext {
                        $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
                        $this->lang = Language::factory( $code );
                } elseif ( $this->lang === null ) {
-                       $this->recursion = true;
+                       $this->languageRecursion = true;
 
                        try {
                                $request = $this->getRequest();
@@ -348,7 +354,7 @@ class RequestContext implements IContextSource, MutableContext {
                                        $this->lang = $obj;
                                }
                        } finally {
-                               unset( $this->recursion );
+                               $this->languageRecursion = false;
                        }
                }
 
index e099b38..09314ed 100644 (file)
@@ -32,24 +32,24 @@ use Wikimedia\Rdbms\ILoadBalancer;
  * @author Daniel Kinzler
  */
 abstract class DBAccessBase implements IDBAccessObject {
-       /**
-        * @var string|bool $wiki The target wiki's name. This must be an ID
-        * that LBFactory can understand.
-        */
-       protected $wiki = false;
+       /** @var ILoadBalancer */
+       private $lb;
+
+       /** @var string|bool The target wiki's DB domain */
+       protected $dbDomain = false;
 
        /**
-        * @param string|bool $wiki The target wiki's name. This must be an ID
-        * that LBFactory can understand.
+        * @param string|bool $dbDomain The target wiki's DB domain
         */
-       public function __construct( $wiki = false ) {
-               $this->wiki = $wiki;
+       public function __construct( $dbDomain = false ) {
+               $this->dbDomain = $dbDomain;
+               $this->lb = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
+                       ->getMainLB( $dbDomain );
        }
 
        /**
         * Returns a database connection.
         *
-        * @see wfGetDB()
         * @see LoadBalancer::getConnection()
         *
         * @since 1.21
@@ -60,9 +60,7 @@ abstract class DBAccessBase implements IDBAccessObject {
         * @return IDatabase
         */
        protected function getConnection( $id, array $groups = [] ) {
-               $loadBalancer = $this->getLoadBalancer();
-
-               return $loadBalancer->getConnection( $id, $groups, $this->wiki );
+               return $this->getLoadBalancer()->getConnectionRef( $id, $groups, $this->dbDomain );
        }
 
        /**
@@ -73,12 +71,10 @@ abstract class DBAccessBase implements IDBAccessObject {
         * @since 1.21
         *
         * @param IDatabase $db The database connection to release.
+        * @deprecated Since 1.34
         */
        protected function releaseConnection( IDatabase $db ) {
-               if ( $this->wiki !== false ) {
-                       $loadBalancer = $this->getLoadBalancer();
-                       $loadBalancer->reuseConnection( $db );
-               }
+               // no-op
        }
 
        /**
@@ -90,8 +86,7 @@ abstract class DBAccessBase implements IDBAccessObject {
         *
         * @return ILoadBalancer The database load balancer object
         */
-       public function getLoadBalancer() {
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-               return $lbFactory->getMainLB( $this->wiki );
+       protected function getLoadBalancer() {
+               return $this->lb;
        }
 }
index e877836..6bcb0e6 100644 (file)
@@ -67,6 +67,30 @@ class MWDebug {
         */
        protected static $deprecationWarnings = [];
 
+       /**
+        * @internal For use by Setup.php only.
+        */
+       public static function setup() {
+               global $wgDebugToolbar,
+                       $wgUseCdn, $wgUseFileCache, $wgCommandLineMode;
+
+               if (
+                       // Easy to forget to falsify $wgDebugToolbar for static caches.
+                       // If file cache or CDN cache is on, just disable this (DWIMD).
+                       $wgUseCdn ||
+                       $wgUseFileCache ||
+                       // Keep MWDebug off on CLI. This prevents MWDebug from eating up
+                       // all the memory for logging SQL queries in maintenance scripts.
+                       $wgCommandLineMode
+               ) {
+                       return;
+               }
+
+               if ( $wgDebugToolbar ) {
+                       self::init();
+               }
+       }
+
        /**
         * Enabled the debugger and load resource module.
         * This is called by Setup.php when $wgDebugToolbar is true.
index 5a5e507..a48faf1 100644 (file)
@@ -10,10 +10,16 @@ use Psr\Log\AbstractLogger;
  * goal.
  */
 class ConsoleLogger extends AbstractLogger {
+       /**
+        * @param string $channel
+        */
        public function __construct( $channel ) {
                $this->channel = $channel;
        }
 
+       /**
+        * @inheritDoc
+        */
        public function log( $level, $message, array $context = [] ) {
                fwrite( STDERR, "[$level] " .
                        LegacyLogger::format( $this->channel, $message, $context ) );
index 66ce9a3..ddffaa3 100644 (file)
@@ -39,8 +39,9 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function merge( MergeableUpdate $update ) {
-               /** @var CdnCacheUpdate $update */
+               /** @var self $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var self $update';
 
                $this->urls = array_merge( $this->urls, $update->urls );
        }
index 1691da2..d1b592d 100644 (file)
@@ -41,8 +41,9 @@ class JobQueueEnqueueUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function merge( MergeableUpdate $update ) {
-               /** @var JobQueueEnqueueUpdate $update */
+               /** @var self $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var self $update';
 
                foreach ( $update->jobsByDomain as $domain => $jobs ) {
                        $this->jobsByDomain[$domain] = $this->jobsByDomain[$domain] ?? [];
index c499d08..7f56a36 100644 (file)
@@ -42,8 +42,9 @@ class MessageCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        }
 
        public function merge( MergeableUpdate $update ) {
-               /** @var MessageCacheUpdate $update */
+               /** @var self $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var self $update';
 
                foreach ( $update->replacements as $code => $messages ) {
                        $this->replacements[$code] = array_merge( $this->replacements[$code] ?? [], $messages );
index 11e9337..dbd7c50 100644 (file)
@@ -56,6 +56,7 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
        public function merge( MergeableUpdate $update ) {
                /** @var SiteStatsUpdate $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var SiteStatsUpdate $update';
 
                foreach ( self::$counters as $field ) {
                        $this->$field += $update->$field;
index 687dfbe..4333c94 100644 (file)
@@ -46,6 +46,7 @@ class UserEditCountUpdate implements DeferrableUpdate, MergeableUpdate {
        public function merge( MergeableUpdate $update ) {
                /** @var UserEditCountUpdate $update */
                Assert::parameterType( __CLASS__, $update, '$update' );
+               '@phan-var UserEditCountUpdate $update';
 
                foreach ( $update->infoByUser as $userId => $info ) {
                        if ( !isset( $this->infoByUser[$userId] ) ) {
index b8697e5..8a5caa2 100644 (file)
@@ -544,7 +544,7 @@ class DifferenceEngine extends ContextSource {
                        if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
                                'edit', $user, $this->mNewPage
                        ) ) {
-                               if ( $this->mNewRev->isCurrent() && $permissionManager->userCan(
+                               if ( $this->mNewRev->isCurrent() && $permissionManager->quickUserCan(
                                        'rollback', $user, $this->mNewPage
                                ) ) {
                                        $rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext(),
@@ -1511,10 +1511,6 @@ class DifferenceEngine extends ContextSource {
        private function userCanEdit( Revision $rev ) {
                $user = $this->getUser();
 
-               if ( !$rev->getContentHandler()->supportsDirectEditing() ) {
-                       return false;
-               }
-
                if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
                        return false;
                }
index 6a3e819..17fa146 100644 (file)
@@ -213,50 +213,6 @@ class ArchivedFile {
                return $file;
        }
 
-       /**
-        * Fields in the filearchive table
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return string[]
-        */
-       static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->fa_user or $row->fa_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               wfDeprecated( __METHOD__, '1.31' );
-               return [
-                       'fa_id',
-                       'fa_name',
-                       'fa_archive_name',
-                       'fa_storage_key',
-                       'fa_storage_group',
-                       'fa_size',
-                       'fa_bits',
-                       'fa_width',
-                       'fa_height',
-                       'fa_metadata',
-                       'fa_media_type',
-                       'fa_major_mime',
-                       'fa_minor_mime',
-                       'fa_user',
-                       'fa_user_text',
-                       'fa_actor' => 'NULL',
-                       'fa_timestamp',
-                       'fa_deleted',
-                       'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
-                       'fa_sha1',
-               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'fa_description' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new archivedfile object.
index 3090632..ceb8dda 100644 (file)
@@ -202,44 +202,6 @@ class LocalFile extends File {
                }
        }
 
-       /**
-        * Fields in the image table
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return string[]
-        */
-       static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->img_user or $row->img_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               return [
-                       'img_name',
-                       'img_size',
-                       'img_width',
-                       'img_height',
-                       'img_metadata',
-                       'img_bits',
-                       'img_media_type',
-                       'img_major_mime',
-                       'img_minor_mime',
-                       'img_user',
-                       'img_user_text',
-                       'img_actor' => 'NULL',
-                       'img_timestamp',
-                       'img_sha1',
-               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new localfile object.
@@ -1449,8 +1411,6 @@ class LocalFile extends File {
                $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = [],
                $createNullRevision = true, $revert = false
        ) {
-               global $wgActorTableSchemaMigrationStage;
-
                if ( is_null( $user ) ) {
                        global $wgUser;
                        $user = $wgUser;
@@ -1553,40 +1513,10 @@ class LocalFile extends File {
                                'oi_major_mime' => 'img_major_mime',
                                'oi_minor_mime' => 'img_minor_mime',
                                'oi_sha1' => 'img_sha1',
+                               'oi_actor' => 'img_actor',
                        ];
                        $joins = [];
 
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                               $fields['oi_user'] = 'img_user';
-                               $fields['oi_user_text'] = 'img_user_text';
-                       }
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $fields['oi_actor'] = 'img_actor';
-                       }
-
-                       if (
-                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
-                       ) {
-                               // Upgrade any rows that are still old-style. Otherwise an upgrade
-                               // might be missed if a deletion happens while the migration script
-                               // is running.
-                               $res = $dbw->select(
-                                       [ 'image' ],
-                                       [ 'img_name', 'img_user', 'img_user_text' ],
-                                       [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
-                                       $dbw->update(
-                                               'image',
-                                               [ 'img_actor' => $actorId ],
-                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
-                                               __METHOD__
-                                       );
-                               }
-                       }
-
                        # (T36993) Note: $oldver can be empty here, if the previous
                        # version of the file was broken. Allow registration of the new
                        # version to continue anyway, because that's better than having
index 61faa09..85988f6 100644 (file)
@@ -178,8 +178,6 @@ class LocalFileDeleteBatch {
        }
 
        protected function doDBInserts() {
-               global $wgActorTableSchemaMigrationStage;
-
                $now = time();
                $dbw = $this->file->repo->getMasterDB();
 
@@ -225,7 +223,8 @@ class LocalFileDeleteBatch {
                                'fa_minor_mime' => 'img_minor_mime',
                                'fa_description_id' => 'img_description_id',
                                'fa_timestamp' => 'img_timestamp',
-                               'fa_sha1' => 'img_sha1'
+                               'fa_sha1' => 'img_sha1',
+                               'fa_actor' => 'img_actor',
                        ];
                        $joins = [];
 
@@ -234,37 +233,6 @@ class LocalFileDeleteBatch {
                                $commentStore->insert( $dbw, 'fa_deleted_reason', $this->reason )
                        );
 
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
-                               $fields['fa_user'] = 'img_user';
-                               $fields['fa_user_text'] = 'img_user_text';
-                       }
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
-                               $fields['fa_actor'] = 'img_actor';
-                       }
-
-                       if (
-                               ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
-                       ) {
-                               // Upgrade any rows that are still old-style. Otherwise an upgrade
-                               // might be missed if a deletion happens while the migration script
-                               // is running.
-                               $res = $dbw->select(
-                                       [ 'image' ],
-                                       [ 'img_name', 'img_user', 'img_user_text' ],
-                                       [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
-                                       __METHOD__
-                               );
-                               foreach ( $res as $row ) {
-                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
-                                       $dbw->update(
-                                               'image',
-                                               [ 'img_actor' => $actorId ],
-                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
-                                               __METHOD__
-                                       );
-                               }
-                       }
-
                        $dbw->insertSelect( 'filearchive', $tables, $fields,
                                [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
                }
index 584e001..f5b7d43 100644 (file)
@@ -106,46 +106,6 @@ class OldLocalFile extends LocalFile {
                }
        }
 
-       /**
-        * Fields in the oldimage table
-        * @deprecated since 1.31, use self::getQueryInfo() instead.
-        * @return string[]
-        */
-       static function selectFields() {
-               global $wgActorTableSchemaMigrationStage;
-
-               wfDeprecated( __METHOD__, '1.31' );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
-                       // If code is using this instead of self::getQueryInfo(), there's a
-                       // decent chance it's going to try to directly access
-                       // $row->oi_user or $row->oi_user_text and we can't give it
-                       // useful values here once those aren't being used anymore.
-                       throw new BadMethodCallException(
-                               'Cannot use ' . __METHOD__
-                                       . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
-                       );
-               }
-
-               return [
-                       'oi_name',
-                       'oi_archive_name',
-                       'oi_size',
-                       'oi_width',
-                       'oi_height',
-                       'oi_metadata',
-                       'oi_bits',
-                       'oi_media_type',
-                       'oi_major_mime',
-                       'oi_minor_mime',
-                       'oi_user',
-                       'oi_user_text',
-                       'oi_actor' => 'NULL',
-                       'oi_timestamp',
-                       'oi_deleted',
-                       'oi_sha1',
-               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'oi_description' );
-       }
-
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new oldlocalfile object.
index 91c6e6a..048abbb 100644 (file)
@@ -41,7 +41,7 @@ abstract class HTMLFormField {
         * the input object itself.  It should not implement the surrounding
         * table cells/rows, or labels/help messages.
         *
-        * @param string $value The value to set the input to; eg a default
+        * @param mixed $value The value to set the input to; eg a default
         *     text for a text input.
         *
         * @return string Valid HTML.