resourceloader: Vary module_deps on language (in addition to skin)
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoaderModule.php
index 1d3ffb5..5b030d7 100644 (file)
@@ -374,44 +374,112 @@ abstract class ResourceLoaderModule {
 
        /**
         * Get the files this module depends on indirectly for a given skin.
-        * Currently these are only image files referenced by the module's CSS.
         *
-        * @param string $skin Skin name
+        * These are only image files referenced by the module's stylesheet.
+        *
+        * @param ResourceLoaderContext $context
         * @return array List of files
         */
-       public function getFileDependencies( $skin ) {
+       protected function getFileDependencies( ResourceLoaderContext $context ) {
+               $vary = $context->getSkin() . '|' . $context->getLanguage();
+
                // Try in-object cache first
-               if ( isset( $this->fileDeps[$skin] ) ) {
-                       return $this->fileDeps[$skin];
+               if ( !isset( $this->fileDeps[$vary] ) ) {
+                       $dbr = wfGetDB( DB_SLAVE );
+                       $deps = $dbr->selectField( 'module_deps',
+                               'md_deps',
+                               array(
+                                       'md_module' => $this->getName(),
+                                       'md_skin' => $vary,
+                               ),
+                               __METHOD__
+                       );
+
+                       if ( !is_null( $deps ) ) {
+                               $this->fileDeps[$vary] = self::expandRelativePaths(
+                                       (array)FormatJson::decode( $deps, true )
+                               );
+                       } else {
+                               $this->fileDeps[$vary] = array();
+                       }
                }
+               return $this->fileDeps[$vary];
+       }
 
-               $dbr = wfGetDB( DB_SLAVE );
-               $deps = $dbr->selectField( 'module_deps',
-                       'md_deps',
-                       array(
-                               'md_module' => $this->getName(),
-                               'md_skin' => $skin,
-                       ),
-                       __METHOD__
-               );
+       /**
+        * Set in-object cache for file dependencies.
+        *
+        * This is used to retrieve data in batches. See ResourceLoader::preloadModuleInfo().
+        * To save the data, use saveFileDependencies().
+        *
+        * @param string $skin Skin name
+        * @param array $deps Array of file names
+        */
+       public function setFileDependencies( ResourceLoaderContext $context, $files ) {
+               $vary = $context->getSkin() . '|' . $context->getLanguage();
+               $this->fileDeps[$vary] = $files;
+       }
 
-               if ( !is_null( $deps ) ) {
-                       $this->fileDeps[$skin] = (array)FormatJson::decode( $deps, true );
-               } else {
-                       $this->fileDeps[$skin] = array();
+       /**
+        * Set the files this module depends on indirectly for a given skin.
+        *
+        * @since 1.26
+        * @param ResourceLoaderContext $context
+        * @param array $localFileRefs List of files
+        */
+       protected function saveFileDependencies( ResourceLoaderContext $context, $localFileRefs ) {
+               // Normalise array
+               $localFileRefs = array_values( array_unique( $localFileRefs ) );
+               sort( $localFileRefs );
+
+               try {
+                       // If the list has been modified since last time we cached it, update the cache
+                       if ( $localFileRefs !== $this->getFileDependencies( $context ) ) {
+                               $vary = $context->getSkin() . '|' . $context->getLanguage();
+                               $dbw = wfGetDB( DB_MASTER );
+                               $dbw->replace( 'module_deps',
+                                       array( array( 'md_module', 'md_skin' ) ), array(
+                                               'md_module' => $this->getName(),
+                                               'md_skin' => $vary,
+                                               // Use relative paths to avoid ghost entries when $IP changes (T111481)
+                                               'md_deps' => FormatJson::encode( self::getRelativePaths( $localFileRefs ) ),
+                                       )
+                               );
+                       }
+               } catch ( Exception $e ) {
+                       wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" );
                }
+       }
 
-               return $this->fileDeps[$skin];
+       /**
+        * Make file paths relative to MediaWiki directory.
+        *
+        * This is used to make file paths safe for storing in a database without the paths
+        * becoming stale or incorrect when MediaWiki is moved or upgraded (T111481).
+        *
+        * @since 1.26
+        * @param array $filePaths
+        * @return array
+        */
+       protected static function getRelativePaths( Array $filePaths ) {
+               global $IP;
+               return array_map( function ( $path ) use ( $IP ) {
+                       return RelPath\getRelativePath( $path, $IP );
+               }, $filePaths );
        }
 
        /**
-        * Set preloaded file dependency information. Used so we can load this
-        * information for all modules at once.
-        * @param string $skin Skin name
-        * @param array $deps Array of file names
+        * Expand directories relative to $IP.
+        *
+        * @since 1.26
+        * @param array $filePaths
+        * @return array
         */
-       public function setFileDependencies( $skin, $deps ) {
-               $this->fileDeps[$skin] = $deps;
+       protected static function expandRelativePaths( Array $filePaths ) {
+               global $IP;
+               return array_map( function ( $path ) use ( $IP ) {
+                       return RelPath\joinPath( $IP, $path );
+               }, $filePaths );
        }
 
        /**
@@ -445,8 +513,10 @@ abstract class ResourceLoaderModule {
        }
 
        /**
-        * Set a preloaded message blob last modification timestamp. Used so we
-        * can load this information for all modules at once.
+        * Set in-object cache for message blob time.
+        *
+        * This is used to retrieve data in batches. See ResourceLoader::preloadModuleInfo().
+        *
         * @param string $lang Language code
         * @param int $mtime UNIX timestamp
         */
@@ -454,6 +524,17 @@ abstract class ResourceLoaderModule {
                $this->msgBlobMtime[$lang] = $mtime;
        }
 
+       /**
+        * Get module-specific LESS variables, if any.
+        *
+        * @since 1.26
+        * @param ResourceLoaderContext $context
+        * @return array Module-specific LESS variables.
+        */
+       protected function getLessVars( ResourceLoaderContext $context ) {
+               return array();
+       }
+
        /**
         * Get an array of this module's resources. Ready for serving to the web.
         *
@@ -857,35 +938,6 @@ abstract class ResourceLoaderModule {
         * @return string Hash
         */
        protected static function safeFileHash( $filePath ) {
-               static $cache;
-
-               if ( !$cache ) {
-                       $cache = ObjectCache::newAccelerator( CACHE_NONE );
-               }
-
-               MediaWiki\suppressWarnings();
-               $mtime = filemtime( $filePath );
-               MediaWiki\restoreWarnings();
-               if ( !$mtime ) {
-                       return '';
-               }
-
-               $cacheKey = wfGlobalCacheKey( 'resourceloader', __METHOD__, $filePath );
-               $cachedHash = $cache->get( $cacheKey );
-               if ( isset( $cachedHash['mtime'] ) && $cachedHash['mtime'] === $mtime ) {
-                       return $cachedHash['hash'];
-               }
-
-               MediaWiki\suppressWarnings();
-               $contents = file_get_contents( $filePath );
-               MediaWiki\restoreWarnings();
-               if ( !$contents ) {
-                       return '';
-               }
-
-               $hash = hash( 'md4', $contents );
-               $cache->set( $cacheKey, array( 'mtime' => $mtime, 'hash' => $hash ), 60 * 60 * 24 );
-
-               return $hash;
+               return FileContentsHasher::getFileContentsHash( $filePath );
        }
 }