From 1aac0a2992e3bbec0436c5e01e00bfc4fef3ccc2 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Thu, 27 Apr 2017 12:58:17 -0400 Subject: [PATCH] Wrap parser output in
This will allow CSS to target just the parser output, without also accidentally targeting the edit form, diff tables, and so on. Bug: T37247 Change-Id: If4eb5bf71f94fa366ec4eddb6964e8f4df6b824a Depends-On: I330c6aa4aaee045614b1801ed34bc9e03be69650 Depends-On: I52a518fa44e017841fe78474012cd69823e0a41d --- RELEASE-NOTES-1.30 | 8 ++++-- includes/api/ApiParse.php | 4 +++ includes/api/i18n/en.json | 1 + includes/api/i18n/qqq.json | 1 + includes/cache/MessageCache.php | 6 ++++ includes/parser/Parser.php | 8 ++++++ includes/parser/ParserOptions.php | 28 +++++++++++++++++++ tests/parser/ParserTestRunner.php | 4 +++ tests/parser/parserTests.txt | 1 + tests/phpunit/includes/ExtraParserTest.php | 2 ++ .../includes/content/WikitextContentTest.php | 2 +- tests/phpunit/includes/page/WikiPageTest.php | 8 ++++-- .../phpunit/includes/parser/TagHooksTest.php | 23 +++++++++------ 13 files changed, 83 insertions(+), 13 deletions(-) diff --git a/RELEASE-NOTES-1.30 b/RELEASE-NOTES-1.30 index cdf8ba4421..1351c00f38 100644 --- a/RELEASE-NOTES-1.30 +++ b/RELEASE-NOTES-1.30 @@ -14,7 +14,9 @@ production. documentation of $wgShellLocale for details. === New features in 1.30 === -* … +* (T37247) Output from Parser::parse() will now be wrapped in a div with + class="mw-parser-output" by default. This may be changed or disabled using + ParserOptions::setWrapOutputClass(). === External library changes in 1.30 === @@ -31,7 +33,9 @@ production. * … === Action API changes in 1.30 === -* … +* (T37247) action=parse output will be wrapped in a div with + class="mw-parser-output" by default. This may be changed or disabled using + the new 'wrapoutputclass' parameter. === Action API internal changes in 1.30 === * … diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index d6489688e6..7d22d9c470 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -478,6 +478,9 @@ class ApiParse extends ApiBase { if ( $params['disabletidy'] ) { $popts->setTidy( false ); } + $popts->setWrapOutputClass( + $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass'] + ); $reset = null; $suppressCache = false; @@ -788,6 +791,7 @@ class ApiParse extends ApiBase { 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ], ], ], + 'wrapoutputclass' => 'mw-parser-output', 'pst' => false, 'onlypst' => false, 'effectivelanglinks' => false, diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 7a04cafafb..387e4b6d7a 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -349,6 +349,7 @@ "apihelp-parse-paramvalue-prop-limitreporthtml": "Gives the HTML version of the limit report. Gives no data, when $1disablelimitreport is set.", "apihelp-parse-paramvalue-prop-parsetree": "The XML parse tree of revision content (requires content model $1)", "apihelp-parse-paramvalue-prop-parsewarnings": "Gives the warnings that occurred while parsing content.", + "apihelp-parse-param-wrapoutputclass": "CSS class to use to wrap the parser output.", "apihelp-parse-param-pst": "Do a pre-save transform on the input before parsing it. Only valid when used with text.", "apihelp-parse-param-onlypst": "Do a pre-save transform (PST) on the input, but don't parse it. Returns the same wikitext, after a PST has been applied. Only valid when used with $1text.", "apihelp-parse-param-effectivelanglinks": "Includes language links supplied by extensions (for use with $1prop=langlinks).", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 6e70653463..81adeec437 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -331,6 +331,7 @@ "apihelp-parse-paramvalue-prop-limitreporthtml": "{{doc-apihelp-paramvalue|parse|prop|limitreporthtml}}", "apihelp-parse-paramvalue-prop-parsetree": "{{doc-apihelp-paramvalue|parse|prop|parsetree|params=* $1 - Value of the constant CONTENT_MODEL_WIKITEXT|paramstart=2}}", "apihelp-parse-paramvalue-prop-parsewarnings": "{{doc-apihelp-paramvalue|parse|prop|parsewarnings}}", + "apihelp-parse-param-wrapoutputclass": "{{doc-apihelp-param|parse|wrapoutputclass}}", "apihelp-parse-param-pst": "{{doc-apihelp-param|parse|pst}}", "apihelp-parse-param-onlypst": "{{doc-apihelp-param|parse|onlypst}}", "apihelp-parse-param-effectivelanglinks": "{{doc-apihelp-param|parse|effectivelanglinks}}", diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php index 355aff40ed..5caa7d5f35 100644 --- a/includes/cache/MessageCache.php +++ b/includes/cache/MessageCache.php @@ -193,6 +193,7 @@ class MessageCache { $po = ParserOptions::newFromAnon(); $po->setEditSection( false ); $po->setAllowUnsafeRawHtml( false ); + $po->setWrapOutputClass( false ); return $po; } @@ -202,6 +203,11 @@ class MessageCache { // from malicious sources. As a precaution, disable // the parser tag when parsing messages. $this->mParserOptions->setAllowUnsafeRawHtml( false ); + // Wrapping messages in an extra
is probably not expected. If + // they're outside the content area they probably shouldn't be + // targeted by CSS that's targeting the parser output, and if + // they're inside they already are from the outer div. + $this->mParserOptions->setWrapOutputClass( false ); } return $this->mParserOptions; diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 5b1e86d252..ecee0e22d3 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -589,6 +589,14 @@ class Parser { $this->mTitle->getPrefixedDBkey() ); } } + + # Wrap non-interface parser output in a
so it can be targeted + # with CSS (T37247) + $class = $this->mOptions->getWrapOutputClass(); + if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) { + $text = Html::rawElement( 'div', [ 'class' => $class ], $text ); + } + $this->mOutput->setText( $text ); $this->mRevisionId = $oldRevisionId; diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php index 2cdd8c7075..d4d104231d 100644 --- a/includes/parser/ParserOptions.php +++ b/includes/parser/ParserOptions.php @@ -258,6 +258,12 @@ class ParserOptions { */ private $allowUnsafeRawHtml = true; + /** + * CSS class to use to wrap output from Parser::parse(). + * @var string|false + */ + private $wrapOutputClass = 'mw-parser-output'; + public function getInterwikiMagic() { return $this->mInterwikiMagic; } @@ -481,6 +487,15 @@ class ParserOptions { return $this->allowUnsafeRawHtml; } + /** + * Class to use to wrap output from Parser::parse() + * @since 1.30 + * @return string|bool + */ + public function getWrapOutputClass() { + return $this->wrapOutputClass; + } + public function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); } @@ -629,6 +644,19 @@ class ParserOptions { return wfSetVar( $this->allowUnsafeRawHtml, $x ); } + /** + * CSS class to use to wrap output from Parser::parse() + * @since 1.30 + * @param string|bool $className Set false to disable wrapping. + * @return string|bool Current value + */ + public function setWrapOutputClass( $className ) { + if ( $className === true ) { // DWIM, they probably want the default class name + $className = 'mw-parser-output'; + } + return wfSetVar( $this->wrapOutputClass, $className ); + } + /** * Set the redirect target. * diff --git a/tests/parser/ParserTestRunner.php b/tests/parser/ParserTestRunner.php index f100411f25..f44b0d5e26 100644 --- a/tests/parser/ParserTestRunner.php +++ b/tests/parser/ParserTestRunner.php @@ -747,6 +747,10 @@ class ParserTestRunner { $user = $context->getUser(); $options = ParserOptions::newFromContext( $context ); + if ( !isset( $opts['wrap'] ) ) { + $options->setWrapOutputClass( false ); + } + if ( isset( $opts['tidy'] ) ) { if ( !$this->tidySupport->isEnabled() ) { $this->recorder->skipped( $test, 'tidy extension is not installed' ); diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index e12c136d91..6477356c8a 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -32,6 +32,7 @@ # local format section links in edit comment text as local links # notoc disable table of contents # thumbsize=NNN set the default thumb size to NNNpx for this test +# wrap include the normal wrapper
(since 1.30) # # You can also set the following parser properties via test options: # wgEnableUploads, wgAllowExternalImages, wgMaxTocLevel, diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 4e95a30e09..a4e3bb9493 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -26,6 +26,7 @@ class ExtraParserTest extends MediaWikiTestCase { // FIXME: This test should pass without setting global content language $this->options = ParserOptions::newFromUserAndLang( new User, $contLang ); $this->options->setTemplateCallback( [ __CLASS__, 'statelessFetchTemplate' ] ); + $this->options->setWrapOutputClass( false ); $this->parser = new Parser; MagicWord::clearCache(); @@ -40,6 +41,7 @@ class ExtraParserTest extends MediaWikiTestCase { $title = Title::newFromText( 'Unit test' ); $options = ParserOptions::newFromUser( new User() ); + $options->setWrapOutputClass( false ); $this->assertEquals( "

$longLine

", $this->parser->parse( $longLine, $title, $options )->getText() ); } diff --git a/tests/phpunit/includes/content/WikitextContentTest.php b/tests/phpunit/includes/content/WikitextContentTest.php index 4c69d871b9..b9ce997f23 100644 --- a/tests/phpunit/includes/content/WikitextContentTest.php +++ b/tests/phpunit/includes/content/WikitextContentTest.php @@ -29,7 +29,7 @@ more stuff "WikitextContentTest_testGetParserOutput", CONTENT_MODEL_WIKITEXT, "hello ''world''\n", - "

hello world\n

" + "

hello world\n

\n\n\n
" ], // TODO: more...? ]; diff --git a/tests/phpunit/includes/page/WikiPageTest.php b/tests/phpunit/includes/page/WikiPageTest.php index 6b911bf3a6..556a34813c 100644 --- a/tests/phpunit/includes/page/WikiPageTest.php +++ b/tests/phpunit/includes/page/WikiPageTest.php @@ -549,7 +549,11 @@ class WikiPageTest extends MediaWikiLangTestCase { public static function provideGetParserOutput() { return [ - [ CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "

hello world

" ], + [ + CONTENT_MODEL_WIKITEXT, + "hello ''world''\n", + "

hello world

" + ], // @todo more...? ]; } @@ -566,7 +570,7 @@ class WikiPageTest extends MediaWikiLangTestCase { $text = $po->getText(); $text = trim( preg_replace( '//sm', '', $text ) ); # strip injected comments - $text = preg_replace( '!\s*(

)!sm', '\1', $text ); # don't let tidy confuse us + $text = preg_replace( '!\s*(

|
)!sm', '\1', $text ); # don't let tidy confuse us $this->assertEquals( $expectedHtml, $text ); diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index 12936ee21d..06fe272b29 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -43,18 +43,25 @@ class TagHookTest extends MediaWikiTestCase { return [ [ "foobar" ], [ "foo\nbar" ], [ "foo\rbar" ] ]; } + private function getParserOptions() { + global $wgContLang; + $popt = ParserOptions::newFromUserAndLang( new User, $wgContLang ); + $popt->setWrapOutputClass( false ); + return $popt; + } + /** * @dataProvider provideValidNames */ public function testTagHooks( $tag ) { - global $wgParserConf, $wgContLang; + global $wgParserConf; $parser = new Parser( $wgParserConf ); $parser->setHook( $tag, [ $this, 'tagCallback' ] ); $parserOutput = $parser->parse( "Foo<$tag>BarBaz", Title::newFromText( 'Test' ), - ParserOptions::newFromUserAndLang( new User, $wgContLang ) + $this->getParserOptions() ); $this->assertEquals( "

FooOneBaz\n

", $parserOutput->getText() ); @@ -66,14 +73,14 @@ class TagHookTest extends MediaWikiTestCase { * @expectedException MWException */ public function testBadTagHooks( $tag ) { - global $wgParserConf, $wgContLang; + global $wgParserConf; $parser = new Parser( $wgParserConf ); $parser->setHook( $tag, [ $this, 'tagCallback' ] ); $parser->parse( "Foo<$tag>BarBaz", Title::newFromText( 'Test' ), - ParserOptions::newFromUserAndLang( new User, $wgContLang ) + $this->getParserOptions() ); $this->fail( 'Exception not thrown.' ); } @@ -82,14 +89,14 @@ class TagHookTest extends MediaWikiTestCase { * @dataProvider provideValidNames */ public function testFunctionTagHooks( $tag ) { - global $wgParserConf, $wgContLang; + global $wgParserConf; $parser = new Parser( $wgParserConf ); $parser->setFunctionTagHook( $tag, [ $this, 'functionTagCallback' ], 0 ); $parserOutput = $parser->parse( "Foo<$tag>BarBaz", Title::newFromText( 'Test' ), - ParserOptions::newFromUserAndLang( new User, $wgContLang ) + $this->getParserOptions() ); $this->assertEquals( "

FooOneBaz\n

", $parserOutput->getText() ); @@ -101,7 +108,7 @@ class TagHookTest extends MediaWikiTestCase { * @expectedException MWException */ public function testBadFunctionTagHooks( $tag ) { - global $wgParserConf, $wgContLang; + global $wgParserConf; $parser = new Parser( $wgParserConf ); $parser->setFunctionTagHook( @@ -112,7 +119,7 @@ class TagHookTest extends MediaWikiTestCase { $parser->parse( "Foo<$tag>BarBaz", Title::newFromText( 'Test' ), - ParserOptions::newFromUserAndLang( new User, $wgContLang ) + $this->getParserOptions() ); $this->fail( 'Exception not thrown.' ); } -- 2.20.1