Use classes to apply the 'editfont' preference
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoader.php
index 85a6954..ad1ed49 100644 (file)
@@ -56,17 +56,17 @@ class ResourceLoader implements LoggerAwareInterface {
        protected $moduleInfos = [];
 
        /** @var Config $config */
-       private $config;
+       protected $config;
 
        /**
         * Associative array mapping framework ids to a list of names of test suite modules
-        * like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. )
+        * like [ 'qunit' => [ 'mediawiki.tests.qunit.suites', 'ext.foo.tests', ... ], ... ]
         * @var array
         */
        protected $testModuleNames = [];
 
        /**
-        * E.g. array( 'source-id' => 'http://.../load.php' )
+        * E.g. [ 'source-id' => 'http://.../load.php' ]
         * @var array
         */
        protected $sources = [];
@@ -109,7 +109,7 @@ class ResourceLoader implements LoggerAwareInterface {
                        // Or else Database*::select() will explode, plus it's cheaper!
                        return;
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $skin = $context->getSkin();
                $lang = $context->getLanguage();
 
@@ -140,6 +140,9 @@ class ResourceLoader implements LoggerAwareInterface {
                        }
                }
 
+               // Batched version of ResourceLoaderWikiModule::getTitleInfo
+               ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $moduleNames );
+
                // Prime in-object cache for message blobs for modules with messages
                $modules = [];
                foreach ( $moduleNames as $name ) {
@@ -241,7 +244,7 @@ class ResourceLoader implements LoggerAwareInterface {
                $this->config = $config;
 
                // Add 'local' source first
-               $this->addSource( 'local', wfScript( 'load' ) );
+               $this->addSource( 'local', $config->get( 'LoadScript' ) );
 
                // Add other sources
                $this->addSource( $config->get( 'ResourceLoaderSources' ) );
@@ -433,7 +436,7 @@ class ResourceLoader implements LoggerAwareInterface {
         *
         * Source IDs are typically the same as the Wiki ID or database name (e.g. lowercase a-z).
         *
-        * @param array|string $id Source ID (string), or array( id1 => loadUrl, id2 => loadUrl, ... )
+        * @param array|string $id Source ID (string), or [ id1 => loadUrl, id2 => loadUrl, ... ]
         * @param string|array $loadUrl load.php url (string), or array with loadUrl key for
         *  backwards-compatibility.
         * @throws MWException
@@ -573,7 +576,7 @@ class ResourceLoader implements LoggerAwareInterface {
        /**
         * Get the list of sources.
         *
-        * @return array Like array( id => load.php url, .. )
+        * @return array Like [ id => load.php url, ... ]
         */
        public function getSources() {
                return $this->sources;
@@ -610,17 +613,49 @@ class ResourceLoader implements LoggerAwareInterface {
         *
         * @since 1.26
         * @param ResourceLoaderContext $context
-        * @param array $modules List of ResourceLoaderModule objects
+        * @param string[] $modules List of known module names
         * @return string Hash
         */
-       public function getCombinedVersion( ResourceLoaderContext $context, array $modules ) {
-               if ( !$modules ) {
+       public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
+               if ( !$moduleNames ) {
                        return '';
                }
                $hashes = array_map( function ( $module ) use ( $context ) {
                        return $this->getModule( $module )->getVersionHash( $context );
-               }, $modules );
-               return self::makeHash( implode( $hashes ) );
+               }, $moduleNames );
+               return self::makeHash( implode( '', $hashes ) );
+       }
+
+       /**
+        * Get the expected value of the 'version' query parameter.
+        *
+        * This is used by respond() to set a short Cache-Control header for requests with
+        * information newer than the current server has. This avoids pollution of edge caches.
+        * Typically during deployment. (T117587)
+        *
+        * This MUST match return value of `mw.loader#getCombinedVersion()` client-side.
+        *
+        * @since 1.28
+        * @param ResourceLoaderContext $context
+        * @param string[] $modules List of module names
+        * @return string Hash
+        */
+       public function makeVersionQuery( ResourceLoaderContext $context ) {
+               // As of MediaWiki 1.28, the server and client use the same algorithm for combining
+               // version hashes. There is no technical reason for this to be same, and for years the
+               // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
+               // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
+               // query parameter), then this method must continue to match the JS one.
+               $moduleNames = [];
+               foreach ( $context->getModules() as $name ) {
+                       if ( !$this->getModule( $name ) ) {
+                               // If a versioned request contains a missing module, the version is a mismatch
+                               // as the client considered a module (and version) we don't have.
+                               return '';
+                       }
+                       $moduleNames[] = $name;
+               }
+               return $this->getCombinedVersion( $context, $moduleNames );
        }
 
        /**
@@ -759,10 +794,14 @@ class ResourceLoader implements LoggerAwareInterface {
         */
        protected function sendResponseHeaders( ResourceLoaderContext $context, $etag, $errors ) {
                $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
-               // If a version wasn't specified we need a shorter expiry time for updates
-               // to propagate to clients quickly
-               // If there were errors, we also need a shorter expiry time so we can recover quickly
-               if ( is_null( $context->getVersion() ) || $errors ) {
+               // Use a short cache expiry so that updates propagate to clients quickly, if:
+               // - No version specified (shared resources, e.g. stylesheets)
+               // - There were errors (recover quickly)
+               // - Version mismatch (T117587, T47877)
+               if ( is_null( $context->getVersion() )
+                       || $errors
+                       || $context->getVersion() !== $this->makeVersionQuery( $context )
+               ) {
                        $maxage = $rlMaxage['unversioned']['client'];
                        $smaxage = $rlMaxage['unversioned']['server'];
                // If a version was specified we can use a longer expiry time since changing
@@ -857,7 +896,7 @@ class ResourceLoader implements LoggerAwareInterface {
                $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
                if ( !$good ) {
                        try { // RL always hits the DB on file cache miss...
-                               wfGetDB( DB_SLAVE );
+                               wfGetDB( DB_REPLICA );
                        } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
                                $good = $fileCache->isCacheGood(); // cache existence check
                        }
@@ -1173,7 +1212,7 @@ MESSAGE;
         *    - ResourceLoader::makeLoaderStateScript( $name, $state ):
         *         Set the state of a single module called $name to $state
         *
-        *    - ResourceLoader::makeLoaderStateScript( array( $name => $state, ... ) ):
+        *    - ResourceLoader::makeLoaderStateScript( [ $name => $state, ... ] ):
         *         Set the state of modules with the given names to the given states
         *
         * @param string $name
@@ -1264,14 +1303,14 @@ MESSAGE;
         *     ):
         *        Register a single module.
         *
-        *   - ResourceLoader::makeLoaderRegisterScript( array( $name1, $name2 ) ):
+        *   - ResourceLoader::makeLoaderRegisterScript( [ $name1, $name2 ] ):
         *        Register modules with the given names.
         *
-        *   - ResourceLoader::makeLoaderRegisterScript( array(
-        *        array( $name1, $version1, $dependencies1, $group1, $source1, $skip1 ),
-        *        array( $name2, $version2, $dependencies1, $group2, $source2, $skip2 ),
+        *   - ResourceLoader::makeLoaderRegisterScript( [
+        *        [ $name1, $version1, $dependencies1, $group1, $source1, $skip1 ],
+        *        [ $name2, $version2, $dependencies1, $group2, $source2, $skip2 ],
         *        ...
-        *     ) ):
+        *     ] ):
         *        Registers modules with the given names and parameters.
         *
         * @param string $name Module name
@@ -1329,14 +1368,14 @@ MESSAGE;
         *   - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
         *       Register a single source
         *
-        *   - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $loadUrl, $id2 => $loadUrl, ... ) );
+        *   - ResourceLoader::makeLoaderSourcesScript( [ $id1 => $loadUrl, $id2 => $loadUrl, ... ] );
         *       Register sources with the given IDs and properties.
         *
         * @param string $id Source ID
-        * @param array $properties Source properties (see addSource())
+        * @param string $loadUrl load.php url
         * @return string
         */
-       public static function makeLoaderSourcesScript( $id, $properties = null ) {
+       public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
                if ( is_array( $id ) ) {
                        return Xml::encodeJsCall(
                                'mw.loader.addSource',
@@ -1346,7 +1385,7 @@ MESSAGE;
                } else {
                        return Xml::encodeJsCall(
                                'mw.loader.addSource',
-                               [ $id, $properties ],
+                               [ $id, $loadUrl ],
                                ResourceLoader::inDebugMode()
                        );
                }
@@ -1402,13 +1441,13 @@ MESSAGE;
        /**
         * Convert an array of module names to a packed query string.
         *
-        * For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' )
+        * For example, [ 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ]
         * becomes 'foo.bar,baz|bar.baz,quux'
         * @param array $modules List of module names (strings)
         * @return string Packed query string
         */
        public static function makePackedModulesString( $modules ) {
-               $groups = []; // array( prefix => array( suffixes ) )
+               $groups = []; // [ prefix => [ suffixes ] ]
                foreach ( $modules as $module ) {
                        $pos = strrpos( $module, '.' );
                        $prefix = $pos === false ? '' : substr( $module, 0, $pos );