Merge "EditPage: Don't throw exceptions for invalid content models"
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoader.php
index 9ce2c5a..97a86c3 100644 (file)
@@ -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 ) {
@@ -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
                        }
@@ -1234,9 +1273,9 @@ MESSAGE;
         * Values considered empty:
         *
         * - null
-        * - array()
+        * - []
         * - new XmlJsCode( '{}' )
-        * - new stdClass() // (object) array()
+        * - new stdClass() // (object) []
         *
         * @param Array $array
         */