* mediaWiki.confirmCloseWindow now returns an object of functions, instead of
one function. The callback can't be called directly any more. The callback
function is replaced with confirmCloseWindow.release().
+* BREAKING CHANGE: Added an optional ResouceLoaderContext parameter to
+ ResourceLoaderModule::getDependencies(). Extension classes that override that
+ method should be updated. If they aren't updated, PHP Strict standards
+ warnings will appear when E_STRICT error reporting is enabled. Note: in the
+ near future, this parameter will probably become non-optional.
* Removed maintenance script deleteImageMemcached.php.
* MWFunction::newObj() was removed (deprecated in 1.25).
ObjectFactory::getObjectFromSpec() should be used instead.
* @ingroup Cache
*/
class MessageCache {
+ const FOR_UPDATE = 1; // force message reload
+
/**
* Process local cache of loaded messages that are defined in
* MediaWiki namespace. First array level is a language code,
* is disabled.
*
* @param bool|string $code Language to which load messages
+ * @param integer $mode Use MessageCache::FOR_UPDATE to skip process cache
* @throws MWException
* @return bool
*/
- function load( $code = false ) {
+ function load( $code = false, $mode = null ) {
global $wgUseLocalMessageCache;
if ( !is_string( $code ) ) {
}
# Don't do double loading...
- if ( isset( $this->mLoadedLanguages[$code] ) ) {
+ if ( isset( $this->mLoadedLanguages[$code] ) && $mode != self::FOR_UPDATE ) {
return true;
}
# Hash of the contents is stored in memcache, to detect if local cache goes
# out of date (e.g. due to replace() on some other server)
if ( $wgUseLocalMessageCache ) {
-
$hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
if ( $hash ) {
$cache = $this->getLocalCache( $hash, $code );
*/
function loadFromDB( $code ) {
global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
+
$dbr = wfGetDB( DB_SLAVE );
$cache = array();
* @param mixed $text New contents of the page.
*/
public function replace( $title, $text ) {
- global $wgMaxMsgCacheEntrySize;
+ global $wgMaxMsgCacheEntrySize, $wgContLang;
if ( $this->mDisable ) {
-
return;
}
list( $msg, $code ) = $this->figureMessage( $title );
$cacheKey = wfMemcKey( 'messages', $code );
- $this->load( $code );
$this->lock( $cacheKey );
+ $this->load( $code, self::FOR_UPDATE );
$titleKey = wfMemcKey( 'messages', 'individual', $title );
}
// Update the message in the message blob store
- global $wgContLang;
$blobStore = new MessageBlobStore();
$blobStore->updateMessage( $wgContLang->lcfirst( $msg ) );
break;
case 'number':
- if ( ctype_digit( $value ) ) {
+ if ( ctype_digit( $value ) || is_int( $value ) ) {
$value = (int)$value;
} else {
$value = (float)$value;
}
/**
- * @param MessageBlobStore $blobStore
+ * @since 1.26
+ * @return MessageBlobStore
+ */
+ public function getMessageBlobStore() {
+ return $this->blobStore;
+ }
+
+ /**
* @since 1.25
+ * @param MessageBlobStore $blobStore
*/
public function setMessageBlobStore( MessageBlobStore $blobStore ) {
$this->blobStore = $blobStore;
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
try {
- $blobs = $this->blobStore->get( $this, $modules, $context->getLanguage() );
+ $this->blobStore->get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
$this->logger->warning( 'Prefetching MessageBlobStore failed: {exception}', array(
) );
$this->errors[] = self::formatExceptionNoComment( $e );
}
- } else {
- $blobs = array();
}
foreach ( $missing as $name ) {
// Generate output
$isRaw = false;
foreach ( $modules as $name => $module ) {
- /**
- * @var $module ResourceLoaderModule
- */
-
try {
- $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() && $module->supportsURLLoading() ) {
- $scripts = $module->getScriptURLsForDebug( $context );
- } else {
- $scripts = $module->getScript( $context );
- // rtrim() because there are usually a few line breaks
- // after the last ';'. A new line at EOF, a new line
- // added by ResourceLoaderFileModule::readScriptFiles, etc.
- if ( is_string( $scripts )
- && strlen( $scripts )
- && substr( rtrim( $scripts ), -1 ) !== ';'
- ) {
- // Append semicolon to prevent weird bugs caused by files not
- // terminating their statements right (bug 27054)
- $scripts .= ";\n";
- }
- }
- }
- // Styles
- $styles = array();
- if ( $context->shouldIncludeStyles() ) {
- // Don't create empty stylesheets like array( '' => '' ) for modules
- // that don't *have* any stylesheets (bug 38024).
- $stylePairs = $module->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() && $module->supportsURLLoading() ) {
- $styles = array(
- 'url' => $module->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] = array();
- foreach ( $style as $cssText ) {
- if ( is_string( $cssText ) ) {
- $stylePairs[$media][] = $this->filter( 'minify-css', $cssText );
- }
- }
- } elseif ( is_string( $style ) ) {
- $stylePairs[$media] = $this->filter( 'minify-css', $style );
- }
- }
- }
- // Wrap styles into @media groups as needed and flatten into a numerical array
- $styles = array(
- 'css' => self::makeCombinedStyles( $stylePairs )
- );
- }
- }
- }
-
- // Messages
- $messagesBlob = isset( $blobs[$name] ) ? $blobs[$name] : '{}';
+ $content = $module->getModuleContent( $context );
// Append output
switch ( $context->getOnly() ) {
case 'scripts':
+ $scripts = $content['scripts'];
if ( is_string( $scripts ) ) {
// Load scripts raw...
$out .= $scripts;
}
break;
case 'styles':
+ $styles = $content['styles'];
// We no longer seperate into media, they are all combined now with
// custom media type groups into @media .. {} sections as part of the css string.
// Module returns either an empty array or a numerical array with css strings.
default:
$out .= self::makeLoaderImplementScript(
$name,
- $scripts,
- $styles,
- new XmlJsCode( $messagesBlob ),
- $module->getTemplates()
+ isset( $content['scripts'] ) ? $content['scripts'] : '',
+ isset( $content['styles'] ) ? $content['styles'] : array(),
+ isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : array(),
+ isset( $content['templates'] ) ? $content['templates'] : array()
);
break;
}
/**
* Gets list of names of modules this module depends on.
- *
+ * @param ResourceLoaderContext context
* @return array List of module names
*/
- public function getDependencies() {
+ public function getDependencies( ResourceLoaderContext $context = null ) {
return $this->dependencies;
}
}
/**
+ * @param ResourceLoaderContext $context
* @return array
*/
- public function getDependencies() {
+ public function getDependencies( ResourceLoaderContext $context = null ) {
return array( 'mediawiki.language.init' );
}
}
);
}
- public function getDependencies() {
+ /**
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getDependencies( ResourceLoaderContext $context = null ) {
return array( 'mediawiki.language.init' );
}
protected $msgBlobMtime = array();
// In-object cache for version hash
protected $versionHash = array();
+ // In-object cache for module content
+ protected $contents = array();
// Whether the position returned by getPosition() is defined in the module configuration
// and not a default value
*
* To add dependencies dynamically on the client side, use a custom
* loader script, see getLoaderScript()
+ *
+ * Note: It is expected that $context will be made non-optional in the near
+ * future.
+ *
+ * @param ResourceLoaderContext $context
* @return array List of module names as strings
*/
- public function getDependencies() {
+ public function getDependencies( ResourceLoaderContext $context = null ) {
// Stub, override expected
return array();
}
$this->msgBlobMtime[$lang] = $mtime;
}
+ /**
+ * Get an array of this module's resources. Ready for serving to the web.
+ *
+ * @since 1.26
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getModuleContent( ResourceLoaderContext $context ) {
+ $contextHash = $context->getHash();
+ // Cache this expensive operation. This calls builds the scripts, styles, and messages
+ // content which typically involves filesystem and/or database access.
+ if ( !array_key_exists( $contextHash, $this->contents ) ) {
+ $this->contents[ $contextHash ] = $this->buildContent( $context );
+ }
+ return $this->contents[ $contextHash ];
+ }
+
+ /**
+ * Bundle all resources attached to this module into an array.
+ *
+ * @since 1.26
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ final protected function buildContent( ResourceLoaderContext $context ) {
+ $rl = $context->getResourceLoader();
+
+ // 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.
+ $content = array();
+
+ // 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 );
+ // rtrim() because there are usually a few line breaks
+ // after the last ';'. A new line at EOF, a new line
+ // added by ResourceLoaderFileModule::readScriptFiles, etc.
+ if ( is_string( $scripts )
+ && strlen( $scripts )
+ && substr( rtrim( $scripts ), -1 ) !== ';'
+ ) {
+ // Append semicolon to prevent weird bugs caused by files not
+ // terminating their statements right (bug 27054)
+ $scripts .= ";\n";
+ }
+ }
+ $content['scripts'] = $scripts;
+ }
+
+ // Styles
+ if ( $context->shouldIncludeStyles() ) {
+ $styles = array();
+ // Don't create empty stylesheets like array( '' => '' ) for modules
+ // that don't *have* any stylesheets (bug 38024).
+ $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 = array(
+ '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] = array();
+ foreach ( $style as $cssText ) {
+ if ( is_string( $cssText ) ) {
+ $stylePairs[$media][] = $rl->filter( 'minify-css', $cssText );
+ }
+ }
+ } elseif ( is_string( $style ) ) {
+ $stylePairs[$media] = $rl->filter( 'minify-css', $style );
+ }
+ }
+ }
+ // Wrap styles into @media groups as needed and flatten into a numerical array
+ $styles = array(
+ 'css' => $rl->makeCombinedStyles( $stylePairs )
+ );
+ }
+ }
+ $content['styles'] = $styles;
+ }
+
+ // Messages
+ $blobs = $rl->getMessageBlobStore()->get(
+ $rl,
+ array( $this->getName() => $this ),
+ $context->getLanguage()
+ );
+ if ( isset( $blobs[$this->getName()] ) ) {
+ $content['messagesBlob'] = $blobs[$this->getName()];
+ }
+
+ $templates = $this->getTemplates();
+ if ( $templates ) {
+ $content['templates'] = $templates;
+ }
+
+ return $content;
+ }
+
/**
* Get a string identifying the current version of this module in a given context.
*
}
/**
+ * @param ResourceLoaderContext $context
* @return array
*/
- public function getDependencies() {
+ public function getDependencies( ResourceLoaderContext $context = null ) {
return array( 'mediawiki.language' );
}
$registryData[$name] = array(
'version' => $versionHash,
- 'dependencies' => $module->getDependencies(),
+ 'dependencies' => $module->getDependencies( $context ),
'group' => $module->getGroup(),
'source' => $module->getSource(),
'loader' => $module->getLoaderScript(),
protected $targets = array( 'desktop', 'mobile' );
/**
+ * @param ResourceLoaderContext $context
* @return array List of module names as strings
*/
- public function getDependencies() {
+ public function getDependencies( ResourceLoaderContext $context = null ) {
return array( 'user.defaults' );
}
return array( '' => $this->styles );
}
- public function getDependencies() {
+ public function getDependencies( ResourceLoaderContext $context = null ) {
return $this->dependencies;
}
$this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' );
// In content language -- get base if no derivative
- $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none', false );
+ $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' );
}
/**
* @param string $title Title of page to be created
* @param string $lang Language and content of the created page
* @param string|null $content Content of the created page, or null for a generic string
- * @param bool $createSubPage Set to false if a root page should be created
*/
- protected function makePage( $title, $lang, $content = null, $createSubPage = true ) {
+ protected function makePage( $title, $lang, $content = null ) {
global $wgContLang;
if ( $content === null ) {
$content = $lang;
}
- if ( $lang !== $wgContLang->getCode() || $createSubPage ) {
+ if ( $lang !== $wgContLang->getCode() ) {
$title = "$title/$lang";
}
'Action text is equal to expected text'
);
- $this->assertEquals(
+ $this->assertSame( // ensure types and array key order
$extra['api'],
self::removeApiMetaData( $formatter->formatParametersForApi() ),
'Api log params is equal to expected array'
array(
'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)',
'api' => array(
- 'mergepoint' => '2014-08-04T16:07:10Z',
'dest_ns' => 0,
'dest_title' => 'NewPage',
+ 'mergepoint' => '2014-08-04T16:07:10Z',
),
),
),
'legacy' => true,
'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)',
'api' => array(
- 'mergepoint' => '2014-08-04T16:07:10Z',
'dest_ns' => 0,
'dest_title' => 'NewPage',
+ 'mergepoint' => '2014-08-04T16:07:10Z',
),
),
),
'namespace' => NS_USER,
'title' => 'New user',
'params' => array(
- '4::userid' => '1',
+ '4::userid' => 1,
),
),
array(
'namespace' => NS_USER,
'title' => 'UTSysop',
'params' => array(
- '4::userid' => '1',
+ '4::userid' => 1,
),
),
array(
'namespace' => NS_USER,
'title' => 'UTSysop',
'params' => array(
- '4::userid' => '1',
+ '4::userid' => 1,
),
),
array(
'namespace' => NS_USER,
'title' => 'New user',
'params' => array(
- '4::userid' => '1',
+ '4::userid' => 1,
),
),
array(