'ResourceLoaderJqueryMsgModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderJqueryMsgModule.php',
'ResourceLoaderLanguageDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageDataModule.php',
'ResourceLoaderLanguageNamesModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageNamesModule.php',
+ 'ResourceLoaderLessVarFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLessVarFileModule.php',
'ResourceLoaderMediaWikiUtilModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderMediaWikiUtilModule.php',
'ResourceLoaderModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderModule.php',
'ResourceLoaderOOUIFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderOOUIFileModule.php',
--- /dev/null
+<?php
+
+/**
+ * Subclass with context specific LESS variables
+ */
+class ResourceLoaderLessVarFileModule extends ResourceLoaderFileModule {
+ protected $lessVariables = [
+ 'collapsible-collapse',
+ 'collapsible-expand',
+ ];
+
+ /**
+ * @inheritDoc
+ */
+ public function getMessages() {
+ // Overload so MessageBlobStore can detect updates to messages and purge as needed.
+ return array_merge( $this->messages, $this->lessVariables );
+ }
+
+ /**
+ * Exclude a set of messages from a JSON string representation
+ * @param string $blob
+ * @param array $exclusions
+ * @return array $blob
+ */
+ protected function excludeMessagesFromBlob( $blob, $exclusions ) {
+ $data = json_decode( $blob, true );
+ // unset the LESS variables so that they are not forwarded to JavaScript
+ foreach ( $exclusions as $key ) {
+ unset( $data[$key] );
+ }
+ return $data;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getMessageBlob( ResourceLoaderContext $context ) {
+ $blob = parent::getMessageBlob( $context );
+ return json_encode( $this->excludeMessagesFromBlob( $blob, $this->lessVariables ) );
+ }
+
+ /**
+ * Takes a message and wraps it in quotes for compatibility with LESS parser
+ * (ModifyVars) method so that the variable can be loaded and made available to stylesheets.
+ * Note this does not take care of CSS escaping. That will be taken care of as part
+ * of CSS Janus.
+ * @param string $msg
+ * @return string wrapped LESS variable definition
+ */
+ private static function wrapAndEscapeMessage( $msg ) {
+ return str_replace( "'", "\'", CSSMin::serializeStringValue( $msg ) );
+ }
+
+ /**
+ * @param \ResourceLoaderContext $context
+ * @return array LESS variables
+ */
+ protected function getLessVars( \ResourceLoaderContext $context ) {
+ $blob = parent::getMessageBlob( $context );
+ $lessMessages = $this->excludeMessagesFromBlob( $blob, $this->messages );
+
+ $vars = [];
+ foreach ( $lessMessages as $msgKey => $value ) {
+ $vars['msg-' . $msgKey] = self::wrapAndEscapeMessage( $value );
+ }
+ return $vars;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group ResourceLoader
+ */
+class ResourceLoaderLessVarFileModuleTest extends ResourceLoaderTestCase {
+
+ public static function providerWrapAndEscapeMessage() {
+ return [
+ [
+ "Foo", '"Foo"',
+ ],
+ [
+ "Foo bananas", '"Foo bananas"',
+ ],
+ [
+ "Who's that test? Who's that test? It's Jess!",
+ '"Who\\\'s that test? Who\\\'s that test? It\\\'s Jess!"',
+ ],
+ [
+ 'Hello "he" said',
+ '"Hello \"he\" said"',
+ ],
+ [
+ 'boo";-o-link:javascript:alert(1);color:red;content:"',
+ '"boo\";-o-link:javascript:alert(1);color:red;content:\""',
+ ],
+ [
+ '"jon\'s"',
+ '"\"jon\\\'s\""'
+ ]
+ ];
+ }
+ /**
+ * @dataProvider providerWrapAndEscapeMessage
+ * @covers ResourceLoaderLessVarFileModule::wrapAndEscapeMessage
+ */
+ public function testEscapeMessage( $msg, $expected ) {
+ $method = new ReflectionMethod( ResourceLoaderLessVarFileModule::class, 'wrapAndEscapeMessage' );
+ $method->setAccessible( true );
+ $this->assertEquals( $expected, $method->invoke( ResourceLoaderLessVarFileModule::class, $msg ) );
+ }
+}