Merge "Simplify HTMLTitleTextField::validate"
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoaderModule.php
index 3bf309d..30b2aa7 100644 (file)
@@ -416,7 +416,7 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
 
                        if ( !is_null( $deps ) ) {
                                $this->fileDeps[$vary] = self::expandRelativePaths(
-                                       (array)FormatJson::decode( $deps, true )
+                                       (array)json_decode( $deps, true )
                                );
                        } else {
                                $this->fileDeps[$vary] = [];
@@ -476,7 +476,9 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                                        return; // T124649; avoid write slams
                                }
 
-                               $deps = FormatJson::encode( $localPaths );
+                               // No needless escaping as this isn't HTML output.
+                               // Only stored in the database and parsed in PHP.
+                               $deps = json_encode( $localPaths, JSON_UNESCAPED_SLASHES );
                                $dbw = wfGetDB( DB_MASTER );
                                $dbw->upsert( 'module_deps',
                                        [
@@ -687,79 +689,77 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
                $statStart = microtime( true );
 
-               // Only include properties that are relevant to this context (e.g. only=scripts)
-               // and that are non-empty (e.g. don't include "templates" for modules without
-               // templates). This helps prevent invalidating cache for all modules when new
-               // optional properties are introduced.
+               // This MUST build both scripts and styles, regardless of whether $context->getOnly()
+               // is 'scripts' or 'styles' because the result is used by getVersionHash which
+               // must be consistent regardles of the 'only' filter on the current request.
+               // Also, when introducing new module content resources (e.g. templates, headers),
+               // these should only be included in the array when they are non-empty so that
+               // existing modules not using them do not get their cache invalidated.
                $content = [];
 
                // Scripts
-               if ( $context->shouldIncludeScripts() ) {
-                       // If we are in debug mode, we'll want to return an array of URLs if possible
-                       // However, we can't do this if the module doesn't support it
-                       // We also can't do this if there is an only= parameter, because we have to give
-                       // the module a way to return a load.php URL without causing an infinite loop
-                       if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
-                               $scripts = $this->getScriptURLsForDebug( $context );
-                       } else {
-                               $scripts = $this->getScript( $context );
-                               // Make the script safe to concatenate by making sure there is at least one
-                               // trailing new line at the end of the content. Previously, this looked for
-                               // a semi-colon instead, but that breaks concatenation if the semicolon
-                               // is inside a comment like "// foo();". Instead, simply use a
-                               // line break as separator which matches JavaScript native logic for implicitly
-                               // ending statements even if a semi-colon is missing.
-                               // Bugs: T29054, T162719.
-                               if ( is_string( $scripts )
-                                       && strlen( $scripts )
-                                       && substr( $scripts, -1 ) !== "\n"
-                               ) {
-                                       $scripts .= "\n";
-                               }
+               // If we are in debug mode, we'll want to return an array of URLs if possible
+               // However, we can't do this if the module doesn't support it.
+               // We also can't do this if there is an only= parameter, because we have to give
+               // the module a way to return a load.php URL without causing an infinite loop
+               if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
+                       $scripts = $this->getScriptURLsForDebug( $context );
+               } else {
+                       $scripts = $this->getScript( $context );
+                       // Make the script safe to concatenate by making sure there is at least one
+                       // trailing new line at the end of the content. Previously, this looked for
+                       // a semi-colon instead, but that breaks concatenation if the semicolon
+                       // is inside a comment like "// foo();". Instead, simply use a
+                       // line break as separator which matches JavaScript native logic for implicitly
+                       // ending statements even if a semi-colon is missing.
+                       // Bugs: T29054, T162719.
+                       if ( is_string( $scripts )
+                               && strlen( $scripts )
+                               && substr( $scripts, -1 ) !== "\n"
+                       ) {
+                               $scripts .= "\n";
                        }
-                       $content['scripts'] = $scripts;
                }
+               $content['scripts'] = $scripts;
 
                // Styles
-               if ( $context->shouldIncludeStyles() ) {
-                       $styles = [];
-                       // Don't create empty stylesheets like [ '' => '' ] for modules
-                       // that don't *have* any stylesheets (T40024).
-                       $stylePairs = $this->getStyles( $context );
-                       if ( count( $stylePairs ) ) {
-                               // If we are in debug mode without &only= set, we'll want to return an array of URLs
-                               // See comment near shouldIncludeScripts() for more details
-                               if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
-                                       $styles = [
-                                               'url' => $this->getStyleURLsForDebug( $context )
-                                       ];
-                               } else {
-                                       // Minify CSS before embedding in mw.loader.implement call
-                                       // (unless in debug mode)
-                                       if ( !$context->getDebug() ) {
-                                               foreach ( $stylePairs as $media => $style ) {
-                                                       // Can be either a string or an array of strings.
-                                                       if ( is_array( $style ) ) {
-                                                               $stylePairs[$media] = [];
-                                                               foreach ( $style as $cssText ) {
-                                                                       if ( is_string( $cssText ) ) {
-                                                                               $stylePairs[$media][] =
-                                                                                       ResourceLoader::filter( 'minify-css', $cssText );
-                                                                       }
+               $styles = [];
+               // Don't create empty stylesheets like [ '' => '' ] for modules
+               // that don't *have* any stylesheets (T40024).
+               $stylePairs = $this->getStyles( $context );
+               if ( count( $stylePairs ) ) {
+                       // If we are in debug mode without &only= set, we'll want to return an array of URLs
+                       // See comment near shouldIncludeScripts() for more details
+                       if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
+                               $styles = [
+                                       'url' => $this->getStyleURLsForDebug( $context )
+                               ];
+                       } else {
+                               // Minify CSS before embedding in mw.loader.implement call
+                               // (unless in debug mode)
+                               if ( !$context->getDebug() ) {
+                                       foreach ( $stylePairs as $media => $style ) {
+                                               // Can be either a string or an array of strings.
+                                               if ( is_array( $style ) ) {
+                                                       $stylePairs[$media] = [];
+                                                       foreach ( $style as $cssText ) {
+                                                               if ( is_string( $cssText ) ) {
+                                                                       $stylePairs[$media][] =
+                                                                               ResourceLoader::filter( 'minify-css', $cssText );
                                                                }
-                                                       } elseif ( is_string( $style ) ) {
-                                                               $stylePairs[$media] = ResourceLoader::filter( 'minify-css', $style );
                                                        }
+                                               } elseif ( is_string( $style ) ) {
+                                                       $stylePairs[$media] = ResourceLoader::filter( 'minify-css', $style );
                                                }
                                        }
-                                       // Wrap styles into @media groups as needed and flatten into a numerical array
-                                       $styles = [
-                                               'css' => $rl->makeCombinedStyles( $stylePairs )
-                                       ];
                                }
+                               // Wrap styles into @media groups as needed and flatten into a numerical array
+                               $styles = [
+                                       'css' => $rl->makeCombinedStyles( $stylePairs )
+                               ];
                        }
-                       $content['styles'] = $styles;
                }
+               $content['styles'] = $styles;
 
                // Messages
                $blob = $this->getMessageBlob( $context );
@@ -803,27 +803,17 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
         * @return string Hash (should use ResourceLoader::makeHash)
         */
        public function getVersionHash( ResourceLoaderContext $context ) {
-               // The startup module produces a manifest with versions representing the entire module.
-               // Typically, the request for the startup module itself has only=scripts. That must apply
-               // only to the startup module content, and not to the module version computed here.
-               $context = new DerivativeResourceLoaderContext( $context );
-               $context->setModules( [] );
-               // Version hash must cover all resources, regardless of startup request itself.
-               $context->setOnly( null );
-               // Compute version hash based on content, not debug urls.
-               $context->setDebug( false );
-
                // Cache this somewhat expensive operation. Especially because some classes
                // (e.g. startup module) iterate more than once over all modules to get versions.
                $contextHash = $context->getHash();
                if ( !array_key_exists( $contextHash, $this->versionHash ) ) {
                        if ( $this->enableModuleContentVersion() ) {
-                               // Detect changes directly
+                               // Detect changes directly by hashing the module contents.
                                $str = json_encode( $this->getModuleContent( $context ) );
                        } else {
                                // Infer changes based on definition and other metrics
                                $summary = $this->getDefinitionSummary( $context );
-                               if ( !isset( $summary['_cacheEpoch'] ) ) {
+                               if ( !isset( $summary['_class'] ) ) {
                                        throw new LogicException( 'getDefinitionSummary must call parent method' );
                                }
                                $str = json_encode( $summary );
@@ -893,7 +883,9 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
        public function getDefinitionSummary( ResourceLoaderContext $context ) {
                return [
                        '_class' => static::class,
-                       '_cacheEpoch' => $this->getConfig()->get( 'CacheEpoch' ),
+                       // Make sure that when filter cache for minification is invalidated,
+                       // we also change the HTTP urls and mw.loader.store keys (T176884).
+                       '_cacheVersion' => ResourceLoader::CACHE_VERSION,
                ];
        }