'MachineReadableRCFeedFormatter' => 'includes/rcfeed/MachineReadableRCFeedFormatter.php',
# includes/resourceloader
+ 'DerivativeResourceLoaderContext' =>
+ 'includes/resourceloader/DerivativeResourceLoaderContext.php',
'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
$extraQuery['target'] = $this->mTarget;
}
- // Create keyed-by-group list of module objects from modules list
- $groups = array();
+ // Create keyed-by-source and then keyed-by-group list of module objects from modules list
+ $sortedModules = array();
$resourceLoader = $this->getResourceLoader();
foreach ( $modules as $name ) {
$module = $resourceLoader->getModule( $name );
continue;
}
- $group = $module->getGroup();
- if ( !isset( $groups[$group] ) ) {
- $groups[$group] = array();
- }
- $groups[$group][$name] = $module;
+ $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module;
}
- foreach ( $groups as $group => $grpModules ) {
- // Special handling for user-specific groups
- $user = null;
- if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
- $user = $this->getUser()->getName();
- }
+ foreach ( $sortedModules as $source => $groups ) {
+ foreach ( $groups as $group => $grpModules ) {
+ // Special handling for user-specific groups
+ $user = null;
+ if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
+ $user = $this->getUser()->getName();
+ }
- // Create a fake request based on the one we are about to make so modules return
- // correct timestamp and emptiness data
- $query = ResourceLoader::makeLoaderQuery(
- array(), // modules; not determined yet
- $this->getLanguage()->getCode(),
- $this->getSkin()->getSkinName(),
- $user,
- null, // version; not determined yet
- ResourceLoader::inDebugMode(),
- $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
- $this->isPrintable(),
- $this->getRequest()->getBool( 'handheld' ),
- $extraQuery
- );
- $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
-
- // Extract modules that know they're empty
- foreach ( $grpModules as $key => $module ) {
- // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857)
- // If we're only getting the styles, we don't need to do anything for empty modules.
- if ( $module->isKnownEmpty( $context ) ) {
- unset( $grpModules[$key] );
- if ( $only !== ResourceLoaderModule::TYPE_STYLES ) {
- $links['states'][$key] = 'ready';
+ // Create a fake request based on the one we are about to make so modules return
+ // correct timestamp and emptiness data
+ $query = ResourceLoader::makeLoaderQuery(
+ array(), // modules; not determined yet
+ $this->getLanguage()->getCode(),
+ $this->getSkin()->getSkinName(),
+ $user,
+ null, // version; not determined yet
+ ResourceLoader::inDebugMode(),
+ $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
+ $this->isPrintable(),
+ $this->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
+ $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
+
+ // Extract modules that know they're empty
+ foreach ( $grpModules as $key => $module ) {
+ // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857)
+ // If we're only getting the styles, we don't need to do anything for empty modules.
+ if ( $module->isKnownEmpty( $context ) ) {
+ unset( $grpModules[$key] );
+ if ( $only !== ResourceLoaderModule::TYPE_STYLES ) {
+ $links['states'][$key] = 'ready';
+ }
}
}
- }
- // If there are no non-empty modules, skip this group
- if ( count( $grpModules ) === 0 ) {
- continue;
- }
+ // If there are no non-empty modules, skip this group
+ if ( count( $grpModules ) === 0 ) {
+ continue;
+ }
- // Inline private modules. These can't be loaded through load.php for security
- // reasons, see bug 34907. Note that these modules should be loaded from
- // getHeadScripts() before the first loader call. Otherwise other modules can't
- // properly use them as dependencies (bug 30914)
- if ( $group === 'private' ) {
- if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
- $links['html'] .= Html::inlineStyle(
- $resourceLoader->makeModuleResponse( $context, $grpModules )
- );
- } else {
- $links['html'] .= Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
+ // Inline private modules. These can't be loaded through load.php for security
+ // reasons, see bug 34907. Note that these modules should be loaded from
+ // getHeadScripts() before the first loader call. Otherwise other modules can't
+ // properly use them as dependencies (bug 30914)
+ if ( $group === 'private' ) {
+ if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
+ $links['html'] .= Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $grpModules )
- )
- );
+ );
+ } else {
+ $links['html'] .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ $resourceLoader->makeModuleResponse( $context, $grpModules )
+ )
+ );
+ }
+ $links['html'] .= "\n";
+ continue;
}
- $links['html'] .= "\n";
- continue;
- }
- // Special handling for the user group; because users might change their stuff
- // on-wiki like user pages, or user preferences; we need to find the highest
- // timestamp of these user-changeable modules so we can ensure cache misses on change
- // This should NOT be done for the site group (bug 27564) because anons get that too
- // and we shouldn't be putting timestamps in Squid-cached HTML
- $version = null;
- if ( $group === 'user' ) {
- // Get the maximum timestamp
- $timestamp = 1;
- foreach ( $grpModules as $module ) {
- $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
+ // Special handling for the user group; because users might change their stuff
+ // on-wiki like user pages, or user preferences; we need to find the highest
+ // timestamp of these user-changeable modules so we can ensure cache misses on change
+ // This should NOT be done for the site group (bug 27564) because anons get that too
+ // and we shouldn't be putting timestamps in Squid-cached HTML
+ $version = null;
+ if ( $group === 'user' ) {
+ // Get the maximum timestamp
+ $timestamp = 1;
+ foreach ( $grpModules as $module ) {
+ $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
+ }
+ // Add a version parameter so cache will break when things change
+ $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
}
- // Add a version parameter so cache will break when things change
- $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
- }
- $url = ResourceLoader::makeLoaderURL(
- array_keys( $grpModules ),
- $this->getLanguage()->getCode(),
- $this->getSkin()->getSkinName(),
- $user,
- $version,
- ResourceLoader::inDebugMode(),
- $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
- $this->isPrintable(),
- $this->getRequest()->getBool( 'handheld' ),
- $extraQuery
- );
- if ( $useESI && $wgResourceLoaderUseESI ) {
- $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
- if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
- $link = Html::inlineStyle( $esi );
- } else {
- $link = Html::inlineScript( $esi );
- }
- } else {
- // Automatically select style/script elements
- if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
- $link = Html::linkedStyle( $url );
- } elseif ( $loadCall ) {
- $link = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
- )
- );
+ $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) );
+ $moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
+ $url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery );
+
+ if ( $useESI && $wgResourceLoaderUseESI ) {
+ $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
+ if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::inlineStyle( $esi );
+ } else {
+ $link = Html::inlineScript( $esi );
+ }
} else {
- $link = Html::linkedScript( $url );
-
- // For modules requested directly in the html via <link> or <script>,
- // tell mw.loader they are being loading to prevent duplicate requests.
- foreach ( $grpModules as $key => $module ) {
- // Don't output state=loading for the startup module..
- if ( $key !== 'startup' ) {
- $links['states'][$key] = 'loading';
+ // Automatically select style/script elements
+ if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::linkedStyle( $url );
+ } elseif ( $loadCall ) {
+ $link = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
+ )
+ );
+ } else {
+ $link = Html::linkedScript( $url );
+
+ // For modules requested directly in the html via <link> or <script>,
+ // tell mw.loader they are being loading to prevent duplicate requests.
+ foreach ( $grpModules as $key => $module ) {
+ // Don't output state=loading for the startup module..
+ if ( $key !== 'startup' ) {
+ $links['states'][$key] = 'loading';
+ }
}
}
}
- }
- if ( $group == 'noscript' ) {
- $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n";
- } else {
- $links['html'] .= $link . "\n";
+ if ( $group == 'noscript' ) {
+ $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n";
+ } else {
+ $links['html'] .= $link . "\n";
+ }
}
}
--- /dev/null
+<?php
+/**
+ * Derivative context for resource loader modules.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Kunal Mehta
+ */
+
+/**
+ * Allows changing specific properties of a context object,
+ * without changing the main one. Inspired by DerivativeContext.
+ *
+ * @since 1.24
+ */
+class DerivativeResourceLoaderContext extends ResourceLoaderContext {
+
+ /**
+ * @var ResourceLoaderContext
+ */
+ private $context;
+ protected $modules;
+ protected $language;
+ protected $direction;
+ protected $skin;
+ protected $user;
+ protected $debug;
+ protected $only;
+ protected $version;
+ protected $hash;
+ protected $raw;
+
+ public function __construct( ResourceLoaderContext $context ) {
+ $this->context = $context;
+ }
+
+ public function getModules() {
+ if ( !is_null( $this->modules ) ) {
+ return $this->modules;
+ } else {
+ return $this->context->getModules();
+ }
+ }
+
+ /**
+ * @param string[] $modules
+ */
+ public function setModules( array $modules ) {
+ $this->modules = $modules;
+ }
+
+ public function getLanguage() {
+ if ( !is_null( $this->language ) ) {
+ return $this->language;
+ } else {
+ return $this->context->getLanguage();
+ }
+ }
+
+ /**
+ * @param string $language
+ */
+ public function setLanguage( $language ) {
+ $this->language = $language;
+ $this->direction = null; // Invalidate direction since it might be based on language
+ $this->hash = null;
+ }
+
+ public function getDirection() {
+ if ( !is_null( $this->direction ) ) {
+ return $this->direction;
+ } else {
+ return $this->context->getDirection();
+ }
+ }
+
+ /**
+ * @param string $direction
+ */
+ public function setDirection( $direction ) {
+ $this->direction = $direction;
+ $this->hash = null;
+ }
+
+ public function getSkin() {
+ if ( !is_null( $this->skin ) ) {
+ return $this->skin;
+ } else {
+ return $this->context->getSkin();
+ }
+ }
+
+ /**
+ * @param string $skin
+ */
+ public function setSkin( $skin ) {
+ $this->skin = $skin;
+ $this->hash = null;
+ }
+
+ public function getUser() {
+ if ( !is_null( $this->user ) ) {
+ return $this->user;
+ } else {
+ return $this->context->getUser();
+ }
+ }
+
+ /**
+ * @param string $user
+ */
+ public function setUser( $user ) {
+ $this->user = $user;
+ $this->hash = null;
+ }
+
+ public function getDebug() {
+ if ( !is_null( $this->debug ) ) {
+ return $this->debug;
+ } else {
+ return $this->context->getDebug();
+ }
+ }
+
+ /**
+ * @param bool $debug
+ */
+ public function setDebug( $debug ) {
+ $this->debug = $debug;
+ $this->hash = null;
+ }
+
+ public function getOnly() {
+ if ( !is_null( $this->only ) ) {
+ return $this->only;
+ } else {
+ return $this->context->getOnly();
+ }
+ }
+
+ /**
+ * @param string $only
+ */
+ public function setOnly( $only ) {
+ $this->only = $only;
+ $this->hash = null;
+ }
+
+ public function getVersion() {
+ if ( !is_null( $this->version ) ) {
+ return $this->version;
+ } else {
+ return $this->context->getVersion();
+ }
+ }
+
+ /**
+ * @param string $version
+ */
+ public function setVersion( $version ) {
+ $this->version = $version;
+ $this->hash = null;
+ }
+
+ public function getRaw() {
+ if ( !is_null( $this->raw ) ) {
+ return $this->raw;
+ } else {
+ return $this->context->getRaw();
+ }
+ }
+
+ /**
+ * @param bool $raw
+ */
+ public function setRaw( $raw ) {
+ $this->raw = $raw;
+ }
+
+ public function getRequest() {
+ return $this->context->getRequest();
+ }
+
+ public function getResourceLoader() {
+ return $this->context->getResourceLoader();
+ }
+
+}
return $this->sources;
}
+ /**
+ * Get the URL to the load.php endpoint for the given
+ * ResourceLoader source
+ *
+ * @since 1.24
+ * @param string $source
+ * @throws MWException on an invalid $source name
+ * @return string
+ */
+ public function getLoadScript( $source ) {
+ if ( !isset( $this->sources[$source] ) ) {
+ throw new MWException( "The $source source was never registered in ResourceLoader." );
+ }
+ return $this->sources[$source]['loadScript'];
+ }
+
/**
* Output a response to a load request, including the content-type header.
*
/**
* Build a load.php URL
+ *
+ * @since 1.24
+ * @param string $source name of the ResourceLoader source
+ * @param ResourceLoaderContext $context
+ * @param array $extraQuery
+ * @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
+ */
+ public function createLoaderURL( $source, ResourceLoaderContext $context,
+ $extraQuery = array()
+ ) {
+ $query = self::createLoaderQuery( $context, $extraQuery );
+ $script = $this->getLoadScript( $source );
+
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ return wfExpandUrl( wfAppendQuery( $script, $query ) . '&*', PROTO_RELATIVE );
+ }
+
+ /**
+ * Build a load.php URL
+ * @deprecated since 1.24, use createLoaderURL instead
* @param array $modules Array of module names (strings)
* @param string $lang Language code
* @param string $skin Skin name
return wfExpandUrl( wfAppendQuery( $wgLoadScript, $query ) . '&*', PROTO_RELATIVE );
}
+ /**
+ * Helper for createLoaderURL()
+ *
+ * @since 1.24
+ * @see makeLoaderQuery
+ * @param ResourceLoaderContext $context
+ * @param array $extraQuery
+ * @return array
+ */
+ public static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = array() ) {
+ return self::makeLoaderQuery(
+ $context->getModules(),
+ $context->getLanguage(),
+ $context->getSkin(),
+ $context->getUser(),
+ $context->getVersion(),
+ $context->getDebug(),
+ $context->getOnly(),
+ $context->getRequest()->getBool( 'printable' ),
+ $context->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
+ }
+
/**
* Build a query array (array representation of query string) for load.php. Helper
* function for makeLoaderURL().
* @return bool
*/
public function shouldIncludeScripts() {
- return is_null( $this->only ) || $this->only === 'scripts';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'scripts';
}
/**
* @return bool
*/
public function shouldIncludeStyles() {
- return is_null( $this->only ) || $this->only === 'styles';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'styles';
}
/**
* @return bool
*/
public function shouldIncludeMessages() {
- return is_null( $this->only ) || $this->only === 'messages';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'messages';
}
/**
public function getHash() {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
- $this->getLanguage(), $this->getDirection(), $this->skin, $this->user,
- $this->debug, $this->only, $this->version
+ $this->getLanguage(), $this->getDirection(), $this->getSkin(), $this->getUser(),
+ $this->getDebug(), $this->getOnly(), $this->getVersion()
) );
}
return $this->hash;
* @return array Array of URLs
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
- $url = ResourceLoader::makeLoaderURL(
- array( $this->getName() ),
- $context->getLanguage(),
- $context->getSkin(),
- $context->getUser(),
- $context->getVersion(),
- true, // debug
- 'scripts', // only
- $context->getRequest()->getBool( 'printable' ),
- $context->getRequest()->getBool( 'handheld' )
+ $resourceLoader = $context->getResourceLoader();
+ $derivative = new DerivativeResourceLoaderContext( $context );
+ $derivative->setModules( array( $this->getName() ) );
+ $derivative->setOnly( 'scripts' );
+ $derivative->setDebug( true );
+
+ $url = $resourceLoader->createLoaderURL(
+ $this->getSource(),
+ $derivative
);
+
return array( $url );
}
* @return array array( mediaType => array( URL1, URL2, ... ), ... )
*/
public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
- $url = ResourceLoader::makeLoaderURL(
- array( $this->getName() ),
- $context->getLanguage(),
- $context->getSkin(),
- $context->getUser(),
- $context->getVersion(),
- true, // debug
- 'styles', // only
- $context->getRequest()->getBool( 'printable' ),
- $context->getRequest()->getBool( 'handheld' )
+ $resourceLoader = $context->getResourceLoader();
+ $derivative = new DerivativeResourceLoaderContext( $context );
+ $derivative->setModules( array( $this->getName() ) );
+ $derivative->setOnly( 'styles' );
+ $derivative->setDebug( true );
+
+ $url = $resourceLoader->createLoaderURL(
+ $this->getSource(),
+ $derivative
);
+
return array( 'all' => array( $url ) );
}
),
);
}
+
+ public static function fakeSources() {
+ return array(
+ 'examplewiki' => array(
+ 'loadScript' => '//example.org/w/load.php',
+ 'apiScript' => '//example.org/w/api.php',
+ ),
+ 'example2wiki' => array(
+ 'loadScript' => '//example.com/w/load.php',
+ 'apiScript' => '//example.com/w/api.php',
+ ),
+ );
+ }
+
+ /**
+ * @covers ResourceLoader::getLoadScript
+ */
+ public function testGetLoadScript() {
+ $this->setMwGlobals( 'wgResourceLoaderSources', array() );
+ $rl = new ResourceLoader();
+ $sources = self::fakeSources();
+ $rl->addSource( $sources );
+ foreach ( array( 'examplewiki', 'example2wiki' ) as $name ) {
+ $this->assertEquals( $rl->getLoadScript( $name ), $sources[$name]['loadScript'] );
+ }
+
+ try {
+ $rl->getLoadScript( 'thiswasneverreigstered' );
+ $this->assertTrue( false, 'ResourceLoader::getLoadScript should have thrown an exception' );
+ } catch ( MWException $e ) {
+ $this->assertTrue( true );
+ }
+ }
}
/* Hooks */