Merge "Update OOUI to v0.34.0"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 4 Sep 2019 23:05:53 +0000 (23:05 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 4 Sep 2019 23:05:53 +0000 (23:05 +0000)
20 files changed:
includes/DefaultSettings.php
includes/Setup.php
includes/cache/localisation/LCStoreStaticArray.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/LocalFileMoveBatch.php
includes/import/ImportableUploadRevisionImporter.php
includes/installer/Installer.php
includes/installer/i18n/en.json
includes/installer/i18n/qqq.json
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactoryMulti.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php
includes/resourceloader/ResourceLoader.php
includes/revisiondelete/RevDelFileList.php
maintenance/deleteArchivedFiles.php
maintenance/importImages.php

index 5a874d5..81de1a0 100644 (file)
@@ -3760,19 +3760,16 @@ $wgIncludeLegacyJavaScript = false;
 $wgLegacyJavaScriptGlobals = true;
 
 /**
- * If set to a positive number, ResourceLoader will not generate URLs whose
- * query string is more than this many characters long, and will instead use
- * multiple requests with shorter query strings. This degrades performance,
- * but may be needed if your web server has a low (less than, say 1024)
- * query string length limit or a low value for suhosin.get.max_value_length
- * that you can't increase.
+ * ResourceLoader will not generate URLs whose query string is more than
+ * this many characters long, and will instead use multiple requests with
+ * shorter query strings. This degrades performance, but may be needed based
+ * on the query string limit supported by your web server and/or your user's
+ * web browsers.
  *
- * If set to a negative number, ResourceLoader will assume there is no query
- * string length limit.
- *
- * Defaults to a value based on php configuration.
+ * @since 1.17
+ * @var int
  */
-$wgResourceLoaderMaxQueryLength = false;
+$wgResourceLoaderMaxQueryLength = 2000;
 
 /**
  * If set to true, JavaScript modules loaded from wiki pages will be parsed
index cc9a3f9..d629021 100644 (file)
@@ -439,17 +439,6 @@ if ( $wgMetaNamespace === false ) {
        $wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
 }
 
-// Default value is 2000 or the suhosin limit if it is between 1 and 2000
-if ( $wgResourceLoaderMaxQueryLength === false ) {
-       $suhosinMaxValueLength = (int)ini_get( 'suhosin.get.max_value_length' );
-       if ( $suhosinMaxValueLength > 0 && $suhosinMaxValueLength < 2000 ) {
-               $wgResourceLoaderMaxQueryLength = $suhosinMaxValueLength;
-       } else {
-               $wgResourceLoaderMaxQueryLength = 2000;
-       }
-       unset( $suhosinMaxValueLength );
-}
-
 // Ensure the minimum chunk size is less than PHP upload limits or the maximum
 // upload size.
 $wgMinUploadChunkSize = min(
index 5911656..53893bd 100644 (file)
@@ -121,6 +121,8 @@ class LCStoreStaticArray implements LCStore {
                        'Generated by LCStoreStaticArray.php -- do not edit!'
                );
                file_put_contents( $this->fname, $out );
+               // Release the data to manage the memory in rebuildLocalisationCache
+               unset( $this->data[$this->currentLang] );
                $this->currentLang = null;
                $this->fname = null;
        }
index 8e3355c..84c0a61 100644 (file)
@@ -32,6 +32,7 @@ use Wikimedia\Rdbms\IDatabase;
  * in the wiki's own database. This is the most commonly used repository class.
  *
  * @ingroup FileRepo
+ * @method LocalFile|null newFile( $title, $time = false )
  */
 class LocalRepo extends FileRepo {
        /** @var callable */
index 137119d..21980b9 100644 (file)
@@ -126,10 +126,8 @@ class LocalFileMoveBatch {
        public function execute() {
                $repo = $this->file->repo;
                $status = $repo->newGood();
-               /** @var LocalFile $destFile */
                $destFile = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
                        ->newFile( $this->target );
-               '@phan-var LocalFile $destFile';
 
                $this->file->lock();
                $destFile->lock(); // quickly fail if destination is not available
index e5f4b57..4be13b0 100644 (file)
@@ -114,7 +114,6 @@ class ImportableUploadRevisionImporter implements UploadRevisionImporter {
                                $user
                        );
                } else {
-                       '@phan-var LocalFile $file';
                        $flags = 0;
                        $status = $file->upload(
                                $source,
index 620cdf0..6d1e211 100644 (file)
@@ -1089,14 +1089,16 @@ abstract class Installer {
 
        /**
         * Checks if suhosin.get.max_value_length is set, and if so generate
-        * a warning because it decreases ResourceLoader performance.
+        * a warning because it is incompatible with ResourceLoader.
         * @return bool
         */
        protected function envCheckSuhosinMaxValueLength() {
-               $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
-               if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
-                       // Only warn if the value is below the sane 1024
-                       $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
+               $currentValue = ini_get( 'suhosin.get.max_value_length' );
+               $minRequired = 2000;
+               $recommended = 5000;
+               if ( $currentValue > 0 && $currentValue < $minRequired ) {
+                       $this->showError( 'config-suhosin-max-value-length', $currentValue, $minRequired, $recommended );
+                       return false;
                }
 
                return true;
index 758221f..ba525ca 100644 (file)
@@ -78,7 +78,7 @@
        "config-uploads-not-safe": "<strong>Warning:</strong> Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.\nAlthough MediaWiki checks all uploaded files for security threats, it is highly recommended to [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
        "config-no-cli-uploads-check": "<strong>Warning:</strong> Your default directory for uploads (<code>$1</code>) is not checked for vulnerability\nto arbitrary script execution during the CLI install.",
        "config-brokenlibxml": "Your system has a combination of PHP and libxml2 versions that is buggy and can cause hidden data corruption in MediaWiki and other web applications.\nUpgrade to libxml2 2.7.3 or later ([https://bugs.php.net/bug.php?id=45996 bug filed with PHP]).\nInstallation aborted.",
-       "config-suhosin-max-value-length": "Suhosin is installed and limits the GET parameter <code>length</code> to $1 bytes.\nMediaWiki's ResourceLoader component will work around this limit, but that will degrade performance.\nIf at all possible, you should set <code>suhosin.get.max_value_length</code> to 1024 or higher in <code>php.ini</code>, and set <code>$wgResourceLoaderMaxQueryLength</code> to the same value in <code>LocalSettings.php</code>.",
+       "config-suhosin-max-value-length": "Suhosin is installed and limits the GET parameter <code>length</code> to $1 bytes.\nMediaWiki requires <code>suhosin.get.max_value_length</code> to be at least $2. Disable this setting, or increase this value to $3 in <code>php.ini</code>.",
        "config-using-32bit": "<strong>Warning:</strong> your system appears to be running with 32-bit integers. This is [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit not advised].",
        "config-db-type": "Database type:",
        "config-db-host": "Database host:",
index 42211b4..1f8a3c6 100644 (file)
        "config-uploads-not-safe": "Used as a part of environment check result. Parameters:\n* $1 - name of directory for images: <code>$IP/images/</code>",
        "config-no-cli-uploads-check": "CLI = [[w:Command-line interface|command-line interface]] (i.e. the installer runs as a command-line script, not using HTML interface via an internet browser)",
        "config-brokenlibxml": "Status message in the MediaWiki installer environment checks.",
-       "config-suhosin-max-value-length": "{{doc-important|Do not translate \"length\", \"suhosin.get.max_value_length\", \"php.ini\", \"$wgResourceLoaderMaxQueryLength\" and \"LocalSettings.php\".}}\nMessage shown when PHP parameter <code>suhosin.get.max_value_length</code> is between 0 and 1023 (that max value is hard set in MediaWiki software).",
+       "config-suhosin-max-value-length": "{{doc-important|Do not translate \"length\", \"suhosin.get.max_value_length\", and \"php.ini\".}}\nThis error message is shown when PHP configuration <code>suhosin.get.max_value_length</code> is not high enough.\n\n* $1 - The current value\n* $2 - The minimum required value\n* $3 - The recommended value\n",
        "config-using-32bit": "Warning message shown when installing on a 32-bit system.",
        "config-db-type": "Field label in the MediaWiki installer followed by possible database types.",
        "config-db-host": "Used as label.\n\nAlso used in {{msg-mw|Config-missing-db-host}}.",
index 6aa3f6f..be41ee0 100644 (file)
@@ -1681,12 +1681,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * Returns an optional USE INDEX clause to go after the table, and a
         * string to go at the end of the query.
         *
+        * @see Database::select()
+        *
         * @param array $options Associative array of options to be turned into
         *   an SQL query, valid keys are listed in the function.
         * @return array
-        * @see Database::select()
         */
-       protected function makeSelectOptions( $options ) {
+       protected function makeSelectOptions( array $options ) {
                $preLimitTail = $postLimitTail = '';
                $startOpts = '';
 
index 851a178..7be3b7d 100644 (file)
@@ -64,8 +64,6 @@ abstract class DatabaseMysqlBase extends Database {
        /** @var bool|null */
        protected $defaultBigSelects = null;
 
-       /** @var string|null */
-       private $serverVersion = null;
        /** @var bool|null */
        private $insertSelectIsSafe = null;
        /** @var stdClass|null */
@@ -1102,13 +1100,19 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string
         */
        public function getServerVersion() {
-               // Not using mysql_get_server_info() or similar for consistency: in the handshake,
-               // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
-               // it off (see RPL_VERSION_HACK in include/mysql_com.h).
-               if ( $this->serverVersion === null ) {
-                       $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
-               }
-               return $this->serverVersion;
+               $cache = $this->srvCache;
+               $fname = __METHOD__;
+
+               return $cache->getWithSetCallback(
+                       $cache->makeGlobalKey( 'mysql-server-version', $this->getServer() ),
+                       $cache::TTL_HOUR,
+                       function () use ( $fname ) {
+                               // Not using mysql_get_server_info() or similar for consistency: in the handshake,
+                               // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
+                               // it off (see RPL_VERSION_HACK in include/mysql_com.h).
+                               return $this->selectField( '', 'VERSION()', '', $fname );
+                       }
+               );
        }
 
        /**
index a7ebc86..c075a1b 100644 (file)
@@ -1301,7 +1301,7 @@ SQL;
                return "'" . pg_escape_string( $conn, (string)$s ) . "'";
        }
 
-       public function makeSelectOptions( $options ) {
+       protected function makeSelectOptions( array $options ) {
                $preLimitTail = $postLimitTail = '';
                $startOpts = $useIndex = $ignoreIndex = '';
 
index 657d308..0e2dc8f 100644 (file)
@@ -603,15 +603,10 @@ class DatabaseSqlite extends Database {
                return in_array( 'UNIQUE', $options );
        }
 
-       /**
-        * Filter the options used in SELECT statements
-        *
-        * @param array $options
-        * @return array
-        */
-       function makeSelectOptions( $options ) {
+       protected function makeSelectOptions( array $options ) {
+               // Remove problematic options that the base implementation converts to SQL
                foreach ( $options as $k => $v ) {
-                       if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
+                       if ( is_numeric( $k ) && ( $v === 'FOR UPDATE' || $v === 'LOCK IN SHARE MODE' ) ) {
                                $options[$k] = '';
                        }
                }
index 4b6afe7..6e9591b 100644 (file)
@@ -38,6 +38,9 @@ interface ILBFactory {
        /** @var int Save DB positions, waiting on all DCs */
        const SHUTDOWN_CHRONPROT_SYNC = 2;
 
+       /** @var string Default main LB cluster name (do not change this) */
+       const CLUSTER_MAIN_DEFAULT = 'DEFAULT';
+
        /**
         * Construct a manager of ILoadBalancer objects
         *
index f675b58..ef1f0a6 100644 (file)
@@ -24,6 +24,7 @@
 namespace Wikimedia\Rdbms;
 
 use InvalidArgumentException;
+use UnexpectedValueException;
 
 /**
  * A multi-database, multi-master factory for Wikimedia and similar installations.
@@ -32,64 +33,45 @@ use InvalidArgumentException;
  * @ingroup Database
  */
 class LBFactoryMulti extends LBFactory {
-       /** @var array A map of database names to section names */
-       private $sectionsByDB;
-       /**
-        * @var array A 2-d map. For each section, gives a map of server names to
-        * load ratios
-        */
-       private $sectionLoads;
-       /**
-        * @var array[] Server info associative array
-        * @note The host, hostName and load entries will be overridden
-        */
-       private $serverTemplate;
+       /** @var LoadBalancer[] */
+       private $mainLBs = [];
+       /** @var LoadBalancer[] */
+       private $externalLBs = [];
 
-       /** @var array A 3-d map giving server load ratios for each section and group */
+       /** @var string[] Map of (hostname => IP address) */
+       private $hostsByName = [];
+       /** @var string[] Map of (database name => section name) */
+       private $sectionsByDB = [];
+       /** @var int[][][] Map of (section => group => host => load ratio) */
        private $groupLoadsBySection = [];
-       /** @var array A 3-d map giving server load ratios by DB name */
+       /** @var int[][][] Map of (database => group => host => load ratio) */
        private $groupLoadsByDB = [];
-       /** @var array A map of hostname to IP address */
-       private $hostsByName = [];
-       /** @var array A map of external storage cluster name to server load map */
+       /** @var int[][] Map of (cluster => host => load ratio) */
        private $externalLoads = [];
-       /**
-        * @var array A set of server info keys overriding serverTemplate for
-        * external storage
-        */
-       private $externalTemplateOverrides;
-       /**
-        * @var array A 2-d map overriding serverTemplate and
-        * externalTemplateOverrides on a server-by-server basis. Applies to both
-        * core and external storage
-        */
-       private $templateOverridesByServer;
-       /** @var array A 2-d map overriding the server info by section */
-       private $templateOverridesBySection;
-       /** @var array A 2-d map overriding the server info by external storage cluster */
-       private $templateOverridesByCluster;
-       /** @var array An override array for all master servers */
-       private $masterTemplateOverrides;
-       /**
-        * @var array|bool A map of section name to read-only message. Missing or
-        * false for read/write
-        */
+       /** @var array Server config map ("host", "hostName", "load", and "groupLoads" are ignored) */
+       private $serverTemplate = [];
+       /** @var array Server config map overriding "serverTemplate" for external storage */
+       private $externalTemplateOverrides = [];
+       /** @var array[] Map of (section => server config map overrides) */
+       private $templateOverridesBySection = [];
+       /** @var array[] Map of (cluster => server config map overrides) for external storage */
+       private $templateOverridesByCluster = [];
+       /** @var array Server config override map for all main and external master servers */
+       private $masterTemplateOverrides = [];
+       /** @var array[] Map of (host => server config map overrides) for main and external servers */
+       private $templateOverridesByServer = [];
+       /**  @var string[]|bool[] A map of section name to read-only message */
        private $readOnlyBySection = [];
 
-       /** @var LoadBalancer[] */
-       private $mainLBs = [];
-       /** @var LoadBalancer[] */
-       private $extLBs = [];
-       /** @var string */
-       private $loadMonitorClass = 'LoadMonitor';
+       /** @var string An ILoadMonitor class */
+       private $loadMonitorClass;
+
        /** @var string */
        private $lastDomain;
        /** @var string */
        private $lastSection;
 
        /**
-        * @see LBFactory::__construct()
-        *
         * Template override precedence (highest => lowest):
         *   - templateOverridesByServer
         *   - masterTemplateOverrides
@@ -98,122 +80,108 @@ class LBFactoryMulti extends LBFactory {
         *   - serverTemplate
         * Overrides only work on top level keys (so nested values will not be merged).
         *
-        * Server configuration maps should be of the format Database::factory() requires.
+        * Server config maps should be of the format Database::factory() requires.
         * Additionally, a 'max lag' key should also be set on server maps, indicating how stale the
         * data can be before the load balancer tries to avoid using it. The map can have 'is static'
         * set to disable blocking  replication sync checks (intended for archive servers with
         * unchanging data).
-        *
-        * @param array $conf Parameters of LBFactory::__construct() as well as:
-        *   - sectionsByDB                Map of database names to section names.
-        *   - sectionLoads                2-d map. For each section, gives a map of server names to
-        *                                 load ratios. For example:
+
+        * @see LBFactory::__construct()
+        * @param array $conf Additional parameters include:
+        *   - hostsByName                 Optional (hostname => IP address) map.
+        *   - sectionsByDB                Optional map of (database => section name).
+        *                                 For example:
         *                                 [
-        *                                     'section1' => [
-        *                                         'db1' => 100,
-        *                                         'db2' => 100
-        *                                     ]
+        *                                     'DEFAULT' => 'section1',
+        *                                     'database1' => 'section2'
         *                                 ]
-        *   - serverTemplate              Server configuration map intended for Database::factory().
-        *                                 Note that "host", "hostName" and "load" entries will be
-        *                                 overridden by "sectionLoads" and "hostsByName".
-        *   - groupLoadsBySection         3-d map giving server load ratios for each section/group.
+        *   - sectionLoads                Optional map of (section => host => load ratio); the first
+        *                                 host in each section is the master server for that section.
+        *                                 For example:
+        *                                 [
+        *                                     'dbmaser'    => 0,
+        *                                     'dbreplica1' => 100,
+        *                                     'dbreplica2' => 100
+        *                                 ]
+        *   - groupLoadsBySection         Optional map of (section => group => host => load ratio);
+        *                                 any ILoadBalancer::GROUP_GENERIC group will be ignored.
         *                                 For example:
         *                                 [
         *                                     'section1' => [
         *                                         'group1' => [
-        *                                             'db1' => 100,
-        *                                             'db2' => 100
+        *                                             'dbreplica3  => 100,
+        *                                             'dbreplica4' => 100
         *                                         ]
         *                                     ]
         *                                 ]
-        *   - groupLoadsByDB              3-d map giving server load ratios by DB name.
-        *   - hostsByName                 Map of hostname to IP address.
-        *   - externalLoads               Map of external storage cluster name to server load map.
-        *   - externalTemplateOverrides   Set of server configuration maps overriding
-        *                                 "serverTemplate" for external storage.
-        *   - templateOverridesByServer   2-d map overriding "serverTemplate" and
-        *                                 "externalTemplateOverrides" on a server-by-server basis.
-        *                                 Applies to both core and external storage.
-        *   - templateOverridesBySection  2-d map overriding the server configuration maps by section.
-        *   - templateOverridesByCluster  2-d map overriding the server configuration maps by external
-        *                                 storage cluster.
-        *   - masterTemplateOverrides     Server configuration map overrides for all master servers.
-        *   - loadMonitorClass            Name of the LoadMonitor class to always use.
-        *   - readOnlyBySection           A map of section name to read-only message.
-        *                                 Missing or false for read/write.
+        *   - groupLoadsByDB              Optional (database => group => host => load ratio) map.
+        *   - externalLoads               Optional (cluster => host => load ratio) map.
+        *   - serverTemplate              server config map for Database::factory().
+        *                                 Note that "host", "hostName" and "load" entries will be
+        *                                 overridden by "groupLoadsBySection" and "hostsByName".
+        *   - externalTemplateOverrides   Optional server config map overrides for external
+        *                                 stores; respects the override precedence described above.
+        *   - templateOverridesBySection  Optional (section => server config map overrides) map;
+        *                                 respects the override precedence described above.
+        *   - templateOverridesByCluster  Optional (external cluster => server config map overrides)
+        *                                 map; respects the override precedence described above.
+        *   - masterTemplateOverrides     Optional server config map overrides for masters;
+        *                                 respects the override precedence described above.
+        *   - templateOverridesByServer   Optional (host => server config map overrides) map;
+        *                                 respects the override precedence described above
+        *                                 and applies to both core and external storage.
+        *   - loadMonitorClass            Name of the LoadMonitor class to always use. [optional]
+        *   - readOnlyBySection           Optional map of (section name => message text or false).
+        *                                 String values make sections read only, whereas anything
+        *                                 else does not restrict read/write mode.
         */
        public function __construct( array $conf ) {
                parent::__construct( $conf );
 
-               $required = [ 'sectionsByDB', 'sectionLoads', 'serverTemplate' ];
-               $optional = [ 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
-                       'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer',
-                       'templateOverridesByCluster', 'templateOverridesBySection', 'masterTemplateOverrides',
-                       'readOnlyBySection', 'loadMonitorClass' ];
-
-               foreach ( $required as $key ) {
-                       if ( !isset( $conf[$key] ) ) {
-                               throw new InvalidArgumentException( __CLASS__ . ": $key is required." );
-                       }
-                       $this->$key = $conf[$key];
-               }
-
-               foreach ( $optional as $key ) {
-                       if ( isset( $conf[$key] ) ) {
-                               $this->$key = $conf[$key];
-                       }
+               $this->hostsByName = $conf['hostsByName'] ?? [];
+               $this->sectionsByDB = $conf['sectionsByDB'];
+               $this->groupLoadsBySection = $conf['groupLoadsBySection'] ?? [];
+               foreach ( ( $conf['sectionLoads'] ?? [] ) as $section => $loadByHost ) {
+                       $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] = $loadByHost;
                }
-       }
-
-       /**
-        * @param bool|string $domain
-        * @return string
-        */
-       private function getSectionForDomain( $domain = false ) {
-               if ( $this->lastDomain === $domain ) {
-                       return $this->lastSection;
-               }
-
-               $database = $this->getDatabaseFromDomain( $domain );
-               $section = $this->sectionsByDB[$database] ?? 'DEFAULT';
-               $this->lastSection = $section;
-               $this->lastDomain = $domain;
-
-               return $section;
+               $this->groupLoadsByDB = $conf['groupLoadsByDB'] ?? [];
+               $this->externalLoads = $conf['externalLoads'] ?? [];
+               $this->serverTemplate = $conf['serverTemplate'] ?? [];
+               $this->externalTemplateOverrides = $conf['externalTemplateOverrides'] ?? [];
+               $this->templateOverridesBySection = $conf['templateOverridesBySection'] ?? [];
+               $this->templateOverridesByCluster = $conf['templateOverridesByCluster'] ?? [];
+               $this->masterTemplateOverrides = $conf['masterTemplateOverrides'] ?? [];
+               $this->templateOverridesByServer = $conf['templateOverridesByServer'] ?? [];
+               $this->readOnlyBySection = $conf['readOnlyBySection'] ?? [];
+
+               $this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class;
        }
 
        public function newMainLB( $domain = false ) {
-               $database = $this->getDatabaseFromDomain( $domain );
                $section = $this->getSectionForDomain( $domain );
-               $groupLoads = $this->groupLoadsByDB[$database] ?? [];
-
-               if ( isset( $this->groupLoadsBySection[$section] ) ) {
-                       $groupLoads = array_merge_recursive(
-                               $groupLoads, $this->groupLoadsBySection[$section] );
+               if ( !isset( $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] ) ) {
+                       throw new UnexpectedValueException( "Section '$section' has no hosts defined." );
                }
 
-               $readOnlyReason = $this->readOnlyReason;
-               // Use the LB-specific read-only reason if everything isn't already read-only
-               if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) {
-                       $readOnlyReason = $this->readOnlyBySection[$section];
-               }
-
-               $template = $this->serverTemplate;
-               if ( isset( $this->templateOverridesBySection[$section] ) ) {
-                       $template = $this->templateOverridesBySection[$section] + $template;
-               }
+               $dbGroupLoads = $this->groupLoadsByDB[$this->getDomainDatabase( $domain )] ?? [];
+               unset( $dbGroupLoads[ILoadBalancer::GROUP_GENERIC] ); // cannot override
 
                return $this->newLoadBalancer(
-                       $template,
-                       $this->sectionLoads[$section],
-                       $groupLoads,
-                       $readOnlyReason
+                       array_merge(
+                               $this->serverTemplate,
+                               $this->templateOverridesBySection[$section] ?? []
+                       ),
+                       array_merge( $this->groupLoadsBySection[$section], $dbGroupLoads ),
+                       // Use the LB-specific read-only reason if everything isn't already read-only
+                       is_string( $this->readOnlyReason )
+                               ? $this->readOnlyReason
+                               : ( $this->readOnlyBySection[$section] ?? false )
                );
        }
 
        public function getMainLB( $domain = false ) {
                $section = $this->getSectionForDomain( $domain );
+
                if ( !isset( $this->mainLBs[$section] ) ) {
                        $this->mainLBs[$section] = $this->newMainLB( $domain );
                }
@@ -223,30 +191,26 @@ class LBFactoryMulti extends LBFactory {
 
        public function newExternalLB( $cluster ) {
                if ( !isset( $this->externalLoads[$cluster] ) ) {
-                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
-               }
-               $template = $this->serverTemplate;
-               if ( $this->externalTemplateOverrides ) {
-                       $template = $this->externalTemplateOverrides + $template;
-               }
-               if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
-                       $template = $this->templateOverridesByCluster[$cluster] + $template;
+                       throw new InvalidArgumentException( "Unknown cluster '$cluster'" );
                }
 
                return $this->newLoadBalancer(
-                       $template,
-                       $this->externalLoads[$cluster],
-                       [],
+                       array_merge(
+                               $this->serverTemplate,
+                               $this->externalTemplateOverrides,
+                               $this->templateOverridesByCluster[$cluster] ?? []
+                       ),
+                       [ ILoadBalancer::GROUP_GENERIC => $this->externalLoads[$cluster] ],
                        $this->readOnlyReason
                );
        }
 
        public function getExternalLB( $cluster ) {
-               if ( !isset( $this->extLBs[$cluster] ) ) {
-                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster );
+               if ( !isset( $this->externalLBs[$cluster] ) ) {
+                       $this->externalLBs[$cluster] = $this->newExternalLB( $cluster );
                }
 
-               return $this->extLBs[$cluster];
+               return $this->externalLBs[$cluster];
        }
 
        public function getAllMainLBs() {
@@ -269,20 +233,45 @@ class LBFactoryMulti extends LBFactory {
                return $lbs;
        }
 
+       public function forEachLB( $callback, array $params = [] ) {
+               foreach ( $this->mainLBs as $lb ) {
+                       $callback( $lb, ...$params );
+               }
+               foreach ( $this->externalLBs as $lb ) {
+                       $callback( $lb, ...$params );
+               }
+       }
+
+       /**
+        * @param bool|string $domain
+        * @return string
+        */
+       private function getSectionForDomain( $domain = false ) {
+               if ( $this->lastDomain === $domain ) {
+                       return $this->lastSection;
+               }
+
+               $database = $this->getDomainDatabase( $domain );
+               $section = $this->sectionsByDB[$database] ?? self::CLUSTER_MAIN_DEFAULT;
+               $this->lastSection = $section;
+               $this->lastDomain = $domain;
+
+               return $section;
+       }
+
        /**
         * Make a new load balancer object based on template and load array
         *
-        * @param array $template
-        * @param array $loads
-        * @param array $groupLoads
+        * @param array $serverTemplate Server config map
+        * @param int[][] $groupLoads Map of (group => host => load)
         * @param string|bool $readOnlyReason
         * @return LoadBalancer
         */
-       private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
+       private function newLoadBalancer( $serverTemplate, $groupLoads, $readOnlyReason ) {
                $lb = new LoadBalancer( array_merge(
                        $this->baseLoadBalancerParams(),
                        [
-                               'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
+                               'servers' => $this->makeServerArray( $serverTemplate, $groupLoads ),
                                'loadMonitor' => [ 'class' => $this->loadMonitorClass ],
                                'readOnlyReason' => $readOnlyReason
                        ]
@@ -293,45 +282,37 @@ class LBFactoryMulti extends LBFactory {
        }
 
        /**
-        * Make a server array as expected by LoadBalancer::__construct, using a template and load array
+        * Make a server array as expected by LoadBalancer::__construct()
         *
-        * @param array $template
-        * @param array $loads
-        * @param array $groupLoads
-        * @return array
+        * @param array $serverTemplate Server config map
+        * @param int[][] $groupLoads Map of (group => host => load)
+        * @return array[] List of server config maps
         */
-       private function makeServerArray( $template, $loads, $groupLoads ) {
-               $servers = [];
-               $master = true;
-               $groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
-               foreach ( $groupLoadsByServer as $server => $stuff ) {
-                       if ( !isset( $loads[$server] ) ) {
-                               $loads[$server] = 0;
-                       }
+       private function makeServerArray( array $serverTemplate, array $groupLoads ) {
+               // The master server is the first host explicitly listed in the generic load group
+               if ( !$groupLoads[ILoadBalancer::GROUP_GENERIC] ) {
+                       throw new UnexpectedValueException( "Empty generic load array; no master defined." );
                }
-               foreach ( $loads as $serverName => $load ) {
-                       $serverInfo = $template;
-                       if ( $master ) {
-                               $serverInfo['master'] = true;
-                               if ( $this->masterTemplateOverrides ) {
-                                       $serverInfo = $this->masterTemplateOverrides + $serverInfo;
-                               }
-                               $master = false;
-                       } else {
-                               $serverInfo['replica'] = true;
-                       }
-                       if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
-                               $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
-                       }
-                       if ( isset( $groupLoadsByServer[$serverName] ) ) {
-                               $serverInfo['groupLoads'] = $groupLoadsByServer[$serverName];
-                       }
-                       $serverInfo['host'] = $this->hostsByName[$serverName] ?? $serverName;
-                       $serverInfo['hostName'] = $serverName;
-                       $serverInfo['load'] = $load;
-                       $serverInfo += [ 'flags' => IDatabase::DBO_DEFAULT ];
 
-                       $servers[] = $serverInfo;
+               $groupLoadsByHost = $this->reindexGroupLoads( $groupLoads );
+               // Get the ordered map of (host => load); the master server is first
+               $genericLoads = $groupLoads[ILoadBalancer::GROUP_GENERIC];
+               // Implictly append any hosts that only appear in custom load groups
+               $genericLoads += array_fill_keys( array_keys( $groupLoadsByHost ), 0 );
+
+               $servers = [];
+               foreach ( $genericLoads as $host => $load ) {
+                       $servers[] = array_merge(
+                               $serverTemplate,
+                               $servers ? [] : $this->masterTemplateOverrides,
+                               $this->templateOverridesByServer[$host] ?? [],
+                               [
+                                       'host' => $this->hostsByName[$host] ?? $host,
+                                       'hostName' => $host,
+                                       'load' => $load,
+                                       'groupLoads' => $groupLoadsByHost[$host] ?? []
+                               ]
+                       );
                }
 
                return $servers;
@@ -339,14 +320,15 @@ class LBFactoryMulti extends LBFactory {
 
        /**
         * Take a group load array indexed by group then server, and reindex it by server then group
-        * @param array $groupLoads
-        * @return array
+        * @param int[][] $groupLoads Map of (group => host => load)
+        * @return int[][] Map of (host => group => load)
         */
-       private function reindexGroupLoads( $groupLoads ) {
+       private function reindexGroupLoads( array $groupLoads ) {
                $reindexed = [];
-               foreach ( $groupLoads as $group => $loads ) {
-                       foreach ( $loads as $server => $load ) {
-                               $reindexed[$server][$group] = $load;
+
+               foreach ( $groupLoads as $group => $loadByHost ) {
+                       foreach ( $loadByHost as $host => $load ) {
+                               $reindexed[$host][$group] = $load;
                        }
                }
 
@@ -357,18 +339,9 @@ class LBFactoryMulti extends LBFactory {
         * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
         * @return string
         */
-       private function getDatabaseFromDomain( $domain = false ) {
+       private function getDomainDatabase( $domain = false ) {
                return ( $domain === false )
                        ? $this->localDomain->getDatabase()
                        : DatabaseDomain::newFromId( $domain )->getDatabase();
        }
-
-       public function forEachLB( $callback, array $params = [] ) {
-               foreach ( $this->mainLBs as $lb ) {
-                       $callback( $lb, ...$params );
-               }
-               foreach ( $this->extLBs as $lb ) {
-                       $callback( $lb, ...$params );
-               }
-       }
 }
index fd76d88..7e73e5b 100644 (file)
@@ -32,20 +32,20 @@ class LBFactorySimple extends LBFactory {
        /** @var LoadBalancer */
        private $mainLB;
        /** @var LoadBalancer[] */
-       private $extLBs = [];
+       private $externalLBs = [];
 
-       /** @var array[] Map of (server index => server config) */
-       private $servers = [];
-       /** @var array[] Map of (cluster => (server index => server config)) */
-       private $externalClusters = [];
+       /** @var array[] Map of (server index => server config map) */
+       private $mainServers = [];
+       /** @var array[][] Map of (cluster => server index => server config map) */
+       private $externalServersByCluster = [];
 
        /** @var string */
        private $loadMonitorClass;
 
        /**
         * @see LBFactory::__construct()
-        * @param array $conf Parameters of LBFactory::__construct() as well as:
-        *   - servers : list of server configuration maps to Database::factory().
+        * @param array $conf Additional parameters include:
+        *   - servers : list of server config maps to Database::factory().
         *      Additionally, the server maps should have a 'load' key, which is used to decide
         *      how often clients connect to one server verses the others. A 'max lag' key should
         *      also be set on server maps, indicating how stale the data can be before the load
@@ -57,25 +57,30 @@ class LBFactorySimple extends LBFactory {
        public function __construct( array $conf ) {
                parent::__construct( $conf );
 
-               $this->servers = $conf['servers'] ?? [];
-               foreach ( $this->servers as $i => $server ) {
+               $this->mainServers = $conf['servers'] ?? [];
+               foreach ( $this->mainServers as $i => $server ) {
                        if ( $i == 0 ) {
-                               $this->servers[$i]['master'] = true;
+                               $this->mainServers[$i]['master'] = true;
                        } else {
-                               $this->servers[$i]['replica'] = true;
+                               $this->mainServers[$i]['replica'] = true;
                        }
                }
 
-               $this->externalClusters = $conf['externalClusters'] ?? [];
-               $this->loadMonitorClass = $conf['loadMonitorClass'] ?? 'LoadMonitor';
+               foreach ( ( $conf['externalClusters'] ?? [] ) as $cluster => $servers ) {
+                       foreach ( $servers as $index => $server ) {
+                               $this->externalServersByCluster[$cluster][$index] = $server;
+                       }
+               }
+
+               $this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class;
        }
 
        public function newMainLB( $domain = false ) {
-               return $this->newLoadBalancer( $this->servers );
+               return $this->newLoadBalancer( $this->mainServers );
        }
 
        public function getMainLB( $domain = false ) {
-               if ( !$this->mainLB ) {
+               if ( $this->mainLB === null ) {
                        $this->mainLB = $this->newMainLB( $domain );
                }
 
@@ -83,28 +88,28 @@ class LBFactorySimple extends LBFactory {
        }
 
        public function newExternalLB( $cluster ) {
-               if ( !isset( $this->externalClusters[$cluster] ) ) {
-                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"." );
+               if ( !isset( $this->externalServersByCluster[$cluster] ) ) {
+                       throw new InvalidArgumentException( "Unknown cluster '$cluster'." );
                }
 
-               return $this->newLoadBalancer( $this->externalClusters[$cluster] );
+               return $this->newLoadBalancer( $this->externalServersByCluster[$cluster] );
        }
 
        public function getExternalLB( $cluster ) {
-               if ( !isset( $this->extLBs[$cluster] ) ) {
-                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster );
+               if ( !isset( $this->externalLBs[$cluster] ) ) {
+                       $this->externalLBs[$cluster] = $this->newExternalLB( $cluster );
                }
 
-               return $this->extLBs[$cluster];
+               return $this->externalLBs[$cluster];
        }
 
        public function getAllMainLBs() {
-               return [ 'DEFAULT' => $this->getMainLB() ];
+               return [ self::CLUSTER_MAIN_DEFAULT => $this->getMainLB() ];
        }
 
        public function getAllExternalLBs() {
                $lbs = [];
-               foreach ( $this->externalClusters as $cluster => $unused ) {
+               foreach ( array_keys( $this->externalServersByCluster ) as $cluster ) {
                        $lbs[$cluster] = $this->getExternalLB( $cluster );
                }
 
@@ -125,10 +130,10 @@ class LBFactorySimple extends LBFactory {
        }
 
        public function forEachLB( $callback, array $params = [] ) {
-               if ( isset( $this->mainLB ) ) {
+               if ( $this->mainLB !== null ) {
                        $callback( $this->mainLB, ...$params );
                }
-               foreach ( $this->extLBs as $lb ) {
+               foreach ( $this->externalLBs as $lb ) {
                        $callback( $lb, ...$params );
                }
        }
index ca83ff3..693afcf 100644 (file)
@@ -1323,7 +1323,7 @@ MESSAGE;
         *
         * @internal
         * @since 1.32
-        * @param bool|string|array $data
+        * @param mixed $data
         * @return string JSON
         */
        public static function encodeJsonForScript( $data ) {
index 334dccf..ca7bc04 100644 (file)
@@ -110,10 +110,8 @@ class RevDelFileList extends RevDelList {
        }
 
        public function doPostCommitUpdates( array $visibilityChangeMap ) {
-               /** @var LocalFile $file */
                $file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
                        ->newFile( $this->title );
-               '@phan-var LocalFile $file';
                $file->purgeCache();
                $file->purgeDescription();
 
index 7b2ef17..cb95b68 100644 (file)
@@ -66,9 +66,7 @@ class DeleteArchivedFiles extends Maintenance {
                                continue;
                        }
 
-                       /** @var LocalFile $file */
                        $file = $repo->newFile( $row->fa_name );
-                       '@phan-var LocalFile $file';
                        try {
                                $file->lock();
                        } catch ( LocalFileLockError $e ) {
index 7f8e16a..4065978 100644 (file)
@@ -332,7 +332,6 @@ class ImportImages extends Maintenance {
 
                                if ( $this->hasOption( 'dry' ) ) {
                                        $this->output( "done.\n" );
-                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                } elseif ( $image->recordUpload2(
                                        $archive->value,
                                        $summary,