Added fname parameter to the query() call
[lhc/web/wiklou.git] / includes / ResourceLoader.php
index 7799619..363b3a1 100644 (file)
  * @author Trevor Parscal
  */
 
+defined( 'MEDIAWIKI' ) || die( 1 );
+
 /**
  * Dynamic JavaScript and CSS resource loading system
- *
- * @example
- *     // Registers a module with the resource loading system
- *     ResourceLoader::register( 'foo', array(
- *             // Script or list of scripts to include when implementating the module (required)
- *             'script' => 'resources/foo/foo.js',
- *             // List of scripts or lists of scripts to include based on the current language
- *             'locales' => array(
- *                     'en-gb' => 'resources/foo/locales/en-gb.js',
- *             ),
- *             // Script or list of scripts to include only when in debug mode
- *             'debug' => 'resources/foo/debug.js',
- *             // If this module is going to be loaded before the mediawiki module is ready such as jquery or the mediawiki
- *             // module itself, it can be included without special loader wrapping - this will also limit the module to not be
- *             // able to specify needs, custom loaders, styles, themes or messages (any of the options below) - raw scripts
- *             // get registered as 'ready' after the mediawiki module is ready, so they can be named as dependencies
- *             'raw' => false,
- *             // Modules or list of modules which are needed and should be used when generating loader code
- *             'needs' => 'resources/foo/foo.js',
- *             // Script or list of scripts which will cause loader code to not be generated - if you are doing something fancy
- *             // with your dependencies this gives you a way to use custom registration code
- *             'loader' => 'resources/foo/loader.js',
- *             // Style-sheets or list of style-sheets to include
- *             'style' => 'resources/foo/foo.css',
- *             // List of style-sheets or lists of style-sheets to include based on the skin - if no match is found for current
- *             // skin, 'default' is used - if default doesn't exist nothing is added
- *             'themes' => array(
- *                     'default' => 'resources/foo/themes/default/foo.css',
- *                     'vector' => 'resources/foo/themes/vector.foo.css',
- *             ),
- *             // List of keys of messages to include
- *             'messages' => array( 'foo-hello', 'foo-goodbye' ),
- *             // Subclass of ResourceLoaderModule to use for custom modules
- *             'class' => 'ResourceLoaderSiteJSModule',
- *     ) );
- * @example
- *     // Responds to a resource loading request
- *     ResourceLoader::respond( $wgRequest, $wgServer . wfScript( 'load' ) );
  */
 class ResourceLoader {
+
        /* Protected Static Members */
 
        // @var array list of module name/ResourceLoaderModule object pairs
-       protected static $modules = array();
+       protected $modules = array();
 
-       /* Protected Static Methods */
+       /* Protected Methods */
+       
+       /**
+        * Loads information stored in the database about modules
+        * 
+        * This is not inside the module code because it's so much more performant to request all of the information at once
+        * than it is to have each module requests it's own information.
+        * 
+        * @param $modules array list of module names to preload information for
+        * @param $context ResourceLoaderContext context to load the information within
+        */
+       protected function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) {
+               if ( !count( $modules ) ) {
+                       return; # or Database*::select() will explode
+               }
+               $dbr = wfGetDb( DB_SLAVE );
+               $skin = $context->getSkin();
+               $lang = $context->getLanguage();
+               
+               // Get file dependency information
+               $res = $dbr->select( 'module_deps', array( 'md_module', 'md_deps' ), array(
+                               'md_module' => $modules,
+                               'md_skin' => $context->getSkin()
+                       ), __METHOD__
+               );
+               
+               $modulesWithDeps = array();
+               foreach ( $res as $row ) {
+                       $this->modules[$row->md_module]->setFileDependencies( $skin,
+                               FormatJson::decode( $row->md_deps, true )
+                       );
+                       $modulesWithDeps[] = $row->md_module;
+               }
+               // Register the absence of a dependencies row too
+               foreach ( array_diff( $modules, $modulesWithDeps ) as $name ) {
+                       $this->modules[$name]->setFileDependencies( $skin, array() );
+               }
+               
+               // Get message blob mtimes. Only do this for modules with messages
+               $modulesWithMessages = array();
+               $modulesWithoutMessages = array();
+               foreach ( $modules as $name ) {
+                       if ( count( $this->modules[$name]->getMessages() ) ) {
+                               $modulesWithMessages[] = $name;
+                       } else {
+                               $modulesWithoutMessages[] = $name;
+                       }
+               }
+               if ( count( $modulesWithMessages ) ) {
+                       $res = $dbr->select( 'msg_resource', array( 'mr_resource', 'mr_timestamp' ), array(
+                                       'mr_resource' => $modulesWithMessages,
+                                       'mr_lang' => $lang
+                               ), __METHOD__
+                       );
+                       foreach ( $res as $row ) {
+                               $this->modules[$row->mr_resource]->setMsgBlobMtime( $lang, $row->mr_timestamp );
+                       }
+               }
+               foreach ( $modulesWithoutMessages as $name ) {
+                       $this->modules[$name]->setMsgBlobMtime( $lang, 0 );
+               }
+       }
 
        /**
         * Runs text through a filter, caching the filtered result for future calls
@@ -77,11 +103,13 @@ class ResourceLoader {
         * @param $file String: path to file being filtered, (optional: only required for CSS to resolve paths)
         * @return String: filtered data
         */
-       protected static function filter( $filter, $data ) {
+       protected function filter( $filter, $data ) {
                global $wgMemc;
+               wfProfileIn( __METHOD__ );
 
                // For empty or whitespace-only things, don't do any processing
                if ( trim( $data ) === '' ) {
+                       wfProfileOut( __METHOD__ );
                        return $data;
                }
 
@@ -90,6 +118,7 @@ class ResourceLoader {
                $cached = $wgMemc->get( $key );
 
                if ( $cached !== false && $cached !== null ) {
+                       wfProfileOut( __METHOD__ );
                        return $cached;
                }
 
@@ -107,6 +136,7 @@ class ResourceLoader {
                                        break;
                                default:
                                        // Don't cache anything, just pass right through
+                                       wfProfileOut( __METHOD__ );
                                        return $data;
                        }
                } catch ( Exception $exception ) {
@@ -116,42 +146,73 @@ class ResourceLoader {
                // Save to memcached
                $wgMemc->set( $key, $result );
 
+               wfProfileOut( __METHOD__ );
                return $result;
        }
 
-       /* Static Methods */
+       /* Methods */
+
+       /**
+        * Registers core modules and runs registration hooks
+        */
+       public function __construct() {
+               global $IP;
+               
+               wfProfileIn( __METHOD__ );
+               
+               // Register core modules
+               $this->register( include( "$IP/resources/Resources.php" ) );
+               // Register extension modules
+               wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
+               
+               wfProfileOut( __METHOD__ );
+       }
 
        /**
         * Registers a module with the ResourceLoader system.
         *
-        * Note that registering the same object under multiple names is not supported and may silently fail in all
-        * kinds of interesting ways.
+        * Note that registering the same object under multiple names is not supported 
+        * and may silently fail in all kinds of interesting ways.
         *
         * @param $name Mixed: string of name of module or array of name/object pairs
-        * @param $object ResourceLoaderModule: module object (optional when using multiple-registration calling style)
-        * @return Boolean: false if there were any errors, in which case one or more modules were not registered
+        * @param $object ResourceLoaderModule: module object (optional when using 
+        *    multiple-registration calling style)
+        * @return Boolean: false if there were any errors, in which case one or more 
+        *    modules were not registered
         *
-        * @todo We need much more clever error reporting, not just in detailing what happened, but in bringing errors to
-        * the client in a way that they can easily see them if they want to, such as by using FireBug
+        * @todo We need much more clever error reporting, not just in detailing what 
+        *    happened, but in bringing errors to the client in a way that they can 
+        *    easily see them if they want to, such as by using FireBug
         */
-       public static function register( $name, ResourceLoaderModule $object = null ) {
+       public function register( $name, ResourceLoaderModule $object = null ) {
+               wfProfileIn( __METHOD__ );
+               
                // Allow multiple modules to be registered in one call
                if ( is_array( $name ) && !isset( $object ) ) {
                        foreach ( $name as $key => $value ) {
-                               self::register( $key, $value );
+                               $this->register( $key, $value );
                        }
 
+                       wfProfileOut( __METHOD__ );
                        return;
                }
 
                // Disallow duplicate registrations
-               if ( isset( self::$modules[$name] ) ) {
+               if ( isset( $this->modules[$name] ) ) {
                        // A module has already been registered by this name
                        throw new MWException( 'Another module has already been registered as ' . $name );
                }
+               
+               // Validate the input (type hinting lets null through)
+               if ( !( $object instanceof ResourceLoaderModule ) ) {
+                       throw new MWException( 'Invalid ResourceLoader module error. Instances of ResourceLoaderModule expected.' );
+               }
+               
                // Attach module
-               self::$modules[$name] = $object;
+               $this->modules[$name] = $object;
                $object->setName( $name );
+               
+               wfProfileOut( __METHOD__ );
        }
 
        /**
@@ -159,8 +220,8 @@ class ResourceLoader {
         *
         * @return Array: array( modulename => ResourceLoaderModule )
         */
-       public static function getModules() {
-               return self::$modules;
+       public function getModules() {
+               return $this->modules;
        }
 
        /**
@@ -169,55 +230,8 @@ class ResourceLoader {
         * @param $name String: module name
         * @return mixed ResourceLoaderModule or null if not registered
         */
-       public static function getModule( $name ) {
-               return isset( self::$modules[$name] ) ? self::$modules[$name] : null;
-       }
-
-       /**
-        * Gets registration code for all modules, except pre-registered ones listed in self::$preRegisteredModules
-        *
-        * @param $context ResourceLoaderContext object
-        * @return String: JavaScript code for registering all modules with the client loader
-        */
-       public static function getModuleRegistrations( ResourceLoaderContext $context ) {
-               $scripts = '';
-               $registrations = array();
-
-               foreach ( self::$modules as $name => $module ) {
-                       // Support module loader scripts
-                       if ( ( $loader = $module->getLoaderScript() ) !== false ) {
-                               $scripts .= $loader;
-                       }
-                       // Automatically register module
-                       else {
-                               // Modules without dependencies pass two arguments (name, timestamp) to mediaWiki.loader.register()
-                               if ( !count( $module->getDependencies() ) ) {
-                                       $registrations[] = array( $name, $module->getModifiedTime( $context ) );
-                               }
-                               // Modules with dependencies pass three arguments (name, timestamp, dependencies) to mediaWiki.loader.register()
-                               else {
-                                       $registrations[] = array( $name, $module->getModifiedTime( $context ), $module->getDependencies() );
-                               }
-                       }
-               }
-               return $scripts . "mediaWiki.loader.register( " . FormatJson::encode( $registrations ) . " );";
-       }
-
-       /**
-        * Get the highest modification time of all modules, based on a given combination of language code,
-        * skin name and debug mode flag.
-        *
-        * @param $context ResourceLoaderContext object
-        * @return Integer: UNIX timestamp
-        */
-       public static function getHighestModifiedTime( ResourceLoaderContext $context ) {
-               $time = 1; // wfTimestamp() treats 0 as 'now', so that's not a suitable choice
-
-               foreach ( self::$modules as $module ) {
-                       $time = max( $time, $module->getModifiedTime( $context ) );
-               }
-
-               return $time;
+       public function getModule( $name ) {
+               return isset( $this->modules[$name] ) ? $this->modules[$name] : null;
        }
 
        /**
@@ -225,79 +239,98 @@ class ResourceLoader {
         *
         * @param $context ResourceLoaderContext object
         */
-       public static function respond( ResourceLoaderContext $context ) {
+       public function respond( ResourceLoaderContext $context ) {
+               global $wgResourceLoaderMaxage;
+
+               wfProfileIn( __METHOD__ );
+               
                // Split requested modules into two groups, modules and missing
                $modules = array();
                $missing = array();
-
+               
                foreach ( $context->getModules() as $name ) {
-                       if ( isset( self::$modules[$name] ) ) {
-                               $modules[] = $name;
+                       if ( isset( $this->modules[$name] ) ) {
+                               $modules[$name] = $this->modules[$name];
                        } else {
                                $missing[] = $name;
                        }
                }
 
-               // Calculate the mtime and caching maxages for this request. We need this, 304 or no 304
-               $mtime = 1;
-               $maxage = PHP_INT_MAX;
-               $smaxage = PHP_INT_MAX;
-
-               foreach ( $modules as $name ) {
-                       $mtime = max( $mtime, self::$modules[$name]->getModifiedTime( $context ) );
-                       $maxage = min( $maxage, self::$modules[$name]->getClientMaxage() );
-                       $smaxage = min( $smaxage, self::$modules[$name]->getServerMaxage() );
+               // If a version wasn't specified we need a shorter expiry time for updates to 
+               // propagate to clients quickly
+               if ( is_null( $context->getVersion() ) ) {
+                       $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
+                       $smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
+               }
+               // If a version was specified we can use a longer expiry time since changing 
+               // version numbers causes cache misses
+               else {
+                       $maxage = $wgResourceLoaderMaxage['versioned']['client'];
+                       $smaxage = $wgResourceLoaderMaxage['versioned']['server'];
                }
 
-               // Output headers
-               if ( $context->getOnly() === 'styles' ) {
-                       header( 'Content-Type: text/css' );
-               } else {
-                       header( 'Content-Type: text/javascript' );
+               // Preload information needed to the mtime calculation below
+               $this->preloadModuleInfo( array_keys( $modules ), $context );
+
+               // To send Last-Modified and support If-Modified-Since, we need to detect 
+               // the last modified time
+               wfProfileIn( __METHOD__.'-getModifiedTime' );
+               $mtime = 1;
+               foreach ( $modules as $module ) {
+                       // Bypass squid cache if the request includes any private modules
+                       if ( $module->getGroup() === 'private' ) {
+                               $smaxage = 0;
+                       }
+                       // Calculate maximum modified time
+                       $mtime = max( $mtime, $module->getModifiedTime( $context ) );
                }
+               wfProfileOut( __METHOD__.'-getModifiedTime' );
 
+               header( 'Content-Type: ' . ( $context->getOnly() === 'styles' ? 'text/css' : 'text/javascript' ) );
                header( 'Last-Modified: ' . wfTimestamp( TS_RFC2822, $mtime ) );
-               $expires = wfTimestamp( TS_RFC2822, min( $maxage, $smaxage ) + time() );
                header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
-               header( "Expires: $expires" );
+               header( 'Expires: ' . wfTimestamp( TS_RFC2822, min( $maxage, $smaxage ) + time() ) );
 
-               // Check if there's an If-Modified-Since header and respond with a 304 Not Modified if possible
+               // If there's an If-Modified-Since header, respond with a 304 appropriately
                $ims = $context->getRequest()->getHeader( 'If-Modified-Since' );
-
-               if ( $ims !== false && wfTimestamp( TS_UNIX, $ims ) == $mtime ) {
+               if ( $ims !== false && $mtime <= wfTimestamp( TS_UNIX, $ims ) ) {
                        header( 'HTTP/1.0 304 Not Modified' );
                        header( 'Status: 304 Not Modified' );
+                       wfProfileOut( __METHOD__ );
                        return;
                }
 
-               // Use output buffering
-               ob_start();
+               echo $this->makeModuleResponse( $context, $modules, $missing );
+
+               wfProfileOut( __METHOD__ );
+       }
 
+       public function makeModuleResponse( ResourceLoaderContext $context, array $modules, $missing = null ) {
                // Pre-fetch blobs
                $blobs = $context->shouldIncludeMessages() ?
-               MessageBlobStore::get( $modules, $context->getLanguage() ) : array();
+                       MessageBlobStore::get( $this, $modules, $context->getLanguage() ) : array();
 
                // Generate output
-               foreach ( $modules as $name ) {
+               $out = '';
+               foreach ( $modules as $name => $module ) {
+                       wfProfileIn( __METHOD__ . '-' . $name );
+
                        // Scripts
                        $scripts = '';
-
                        if ( $context->shouldIncludeScripts() ) {
-                               $scripts .= self::$modules[$name]->getScript( $context );
+                               $scripts .= $module->getScript( $context ) . "\n";
                        }
 
                        // Styles
                        $styles = array();
-
                        if (
-                               $context->shouldIncludeStyles() && ( count( $styles = self::$modules[$name]->getStyles( $context ) ) )
+                               $context->shouldIncludeStyles() &&
+                               ( count( $styles = $module->getStyles( $context ) ) )
                        ) {
-                               foreach ( $styles as $media => $style ) {
-                                       if ( self::$modules[$name]->getFlip( $context ) ) {
-                                               $styles[$media] = self::filter( 'flip-css', $style );
-                                       }
-                                       if ( !$context->getDebug() ) {
-                                               $styles[$media] = self::filter( 'minify-css', $style );
+                               // Flip CSS on a per-module basis
+                               if ( $this->modules[$name]->getFlip( $context ) ) {
+                                       foreach ( $styles as $media => $style ) {
+                                               $styles[$media] = $this->filter( 'flip-css', $style );
                                        }
                                }
                        }
@@ -305,59 +338,145 @@ class ResourceLoader {
                        // Messages
                        $messages = isset( $blobs[$name] ) ? $blobs[$name] : '{}';
 
-                       // Output
-                       if ( $context->getOnly() === 'styles' ) {
-                               if ( $context->getDebug() ) {
-                                       echo "/* $name */\n";
-                                       foreach ( $styles as $media => $style ) {
-                                               echo "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "\n}\n";
-                                       }
-                               } else {
-                                       foreach ( $styles as $media => $style ) {
-                                               if ( strlen( $style ) ) {
-                                                       echo "@media $media{" . $style . "}";
+                       // Append output
+                       switch ( $context->getOnly() ) {
+                               case 'scripts':
+                                       $out .= $scripts;
+                                       break;
+                               case 'styles':
+                                       $out .= self::makeCombinedStyles( $styles );
+                                       break;
+                               case 'messages':
+                                       $out .= self::makeMessageSetScript( $messages );
+                                       break;
+                               default:
+                                       // Minify CSS before embedding in mediaWiki.loader.implement call (unless in debug mode)
+                                       if ( !$context->getDebug() ) {
+                                               foreach ( $styles as $media => $style ) {
+                                                       $styles[$media] = $this->filter( 'minify-css', $style );
                                                }
                                        }
-                               }
-                       } else if ( $context->getOnly() === 'scripts' ) {
-                               echo $scripts;
-                       } else if ( $context->getOnly() === 'messages' ) {
-                               echo "mediaWiki.msg.set( $messages );\n";
-                       } else {
-                               $styles = FormatJson::encode( $styles );
-                               echo "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n$styles,\n$messages );\n";
+                                       $out .= self::makeLoaderImplementScript( $name, $scripts, $styles, $messages );
+                                       break;
                        }
+                       
+                       wfProfileOut( __METHOD__ . '-' . $name );
                }
 
-               // Update the status of script-only modules
-               if ( $context->getOnly() === 'scripts' && !in_array( 'startup', $modules ) ) {
-                       $statuses = array();
+               // Update module states
+               if ( $context->shouldIncludeScripts() ) {
+                       // Set the state of modules loaded as only scripts to ready
+                       if ( count( $modules ) && $context->getOnly() === 'scripts' && !isset( $modules['startup'] ) ) {
+                               $out .= self::makeLoaderStateScript( array_fill_keys( array_keys( $modules ), 'ready' ) );
+                       }
+                       // Set the state of modules which were requested but unavailable as missing
+                       if ( is_array( $missing ) && count( $missing ) ) {
+                               $out .= self::makeLoaderStateScript( array_fill_keys( $missing, 'missing' ) );
+                       }
+               }
 
-                       foreach ( $modules as $name ) {
-                               $statuses[$name] = 'ready';
+               if ( $context->getDebug() ) {
+                       return $out;
+               } else {
+                       if ( $context->getOnly() === 'styles' ) {
+                               return $this->filter( 'minify-css', $out );
+                       } else {
+                               return $this->filter( 'minify-js', $out );
                        }
+               }
+       }
+       
+       /* Static Methods */
+       
+       public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
+               if ( is_array( $scripts ) ) {
+                       $scripts = implode( $scripts, "\n" );
+               }
+               if ( is_array( $styles ) ) {
+                       $styles = count( $styles ) ? FormatJson::encode( $styles ) : 'null';
+               }
+               if ( is_array( $messages ) ) {
+                       $messages = count( $messages ) ? FormatJson::encode( $messages ) : 'null';
+               }
+               return "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n$styles,\n$messages );\n";
+       }
 
-                       $statuses = FormatJson::encode( $statuses );
-                       echo "mediaWiki.loader.state( $statuses );\n";
+       public static function makeMessageSetScript( $messages ) {
+               if ( is_array( $messages ) ) {
+                       $messages = count( $messages ) ? FormatJson::encode( $messages ) : 'null';
                }
+               return "mediaWiki.msg.set( $messages );\n";
+       }
 
-               // Register missing modules
-               if ( $context->shouldIncludeScripts() ) {
-                       foreach ( $missing as $name ) {
-                               echo "mediaWiki.loader.register( '$name', null, 'missing' );\n";
-                       }
+       public static function makeCombinedStyles( array $styles ) {
+               $out = '';
+               foreach ( $styles as $media => $style ) {
+                       $out .= "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "\n}\n";
                }
+               return $out;
+       }
 
-               // Output the appropriate header
-               if ( $context->getOnly() !== 'styles' ) {
-                       if ( $context->getDebug() ) {
-                               ob_end_flush();
+       public static function makeLoaderStateScript( $name, $state = null ) {
+               if ( is_array( $name ) ) {
+                       $statuses = FormatJson::encode( $name );
+                       return "mediaWiki.loader.state( $statuses );\n";
+               } else {
+                       $name = Xml::escapeJsString( $name );
+                       $state = Xml::escapeJsString( $state );
+                       return "mediaWiki.loader.state( '$name', '$state' );\n";
+               }
+       }
+
+       public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $script ) {
+               $name = Xml::escapeJsString( $name );
+               $version = (int) $version > 1 ? (int) $version : 1;
+               if ( is_array( $dependencies ) ) {
+                       $dependencies = FormatJson::encode( $dependencies );
+               } else if ( is_string( $dependencies ) ) {
+                       $dependencies = "'" . Xml::escapeJsString( $dependencies ) . "'";
+               } else {
+                       $dependencies = 'null';
+               }
+               if ( is_string( $group ) ) {
+                       $group = "'" . Xml::escapeJsString( $group ) . "'";
+               } else {
+                       $group = 'null';
+               }
+               $script = str_replace( "\n", "\n\t", trim( $script ) );
+               return "( function( name, version, dependencies, group ) {\n\t$script\n} )" .
+                       "( '$name', $version, $dependencies, $group );\n";
+       }
+
+       public static function makeLoaderRegisterScript( $name, $version = null, $dependencies = null, $group = null ) {
+               if ( is_array( $name ) ) {
+                       $registrations = FormatJson::encode( $name );
+                       return "mediaWiki.loader.register( $registrations );\n";
+               } else {
+                       $name = Xml::escapeJsString( $name );
+                       $version = (int) $version > 1 ? (int) $version : 1;
+                       if ( is_array( $dependencies ) ) {
+                               $dependencies = FormatJson::encode( $dependencies );
+                       } else if ( is_string( $dependencies ) ) {
+                               $dependencies = "'" . Xml::escapeJsString( $dependencies ) . "'";
                        } else {
-                               echo self::filter( 'minify-js', ob_get_clean() );
+                               $dependencies = 'null';
                        }
+                       if ( is_string( $group ) ) {
+                               $group = "'" . Xml::escapeJsString( $group ) . "'";
+                       } else {
+                               $group = 'null';
+                       }
+                       return "mediaWiki.loader.register( '$name', $version, $dependencies, $group );\n";
                }
        }
-}
 
-// FIXME: Temp hack
-require_once "$IP/resources/Resources.php";
+       public static function makeLoaderConditionalScript( $script ) {
+               $script = str_replace( "\n", "\n\t", trim( $script ) );
+               return "if ( window.mediaWiki ) {\n\t$script\n}\n";
+       }
+
+       public static function makeConfigSetScript( array $configuration ) {
+               $configuration = FormatJson::encode( $configuration );
+               return "mediaWiki.config.set( $configuration );\n";
+       }
+}