And deprecate passing false for ParserOptions::setWrapOutputClass().
There are three cases for the Parser wrapper: the default
mw-parser-output, a custom wrapper, or no wrapper. As things currently
stand, we have to fragment the parser cache on each of these options,
which uses a nontrival amount of storage space (T167784).
Ideally we'd do all the wrapping as a post-cache transform, but
TemplateStyles needs to know the wrapper in use in order to properly
prefix its CSS rules (that's why we added the wrapper in the first
place). So, second best option is to make *un*wrapping be a post-cache
transform and make "custom wrapper" be uncacheable.
This patch does the first bit (unwrapping as a post-cache transform),
and a followup will do the second part once the deprecation process is
satisfied.
Bug: T181846
Change-Id: Iba16e78c41be992467101e7d83e9c3134765b101
12 files changed:
Setting template variables by reference allowed violating the principle of data being
immutable once added to the skin template. In practice, this method was not being
used for that. Rather, setRef() existed as memory optimisation for PHP 4.
Setting template variables by reference allowed violating the principle of data being
immutable once added to the skin template. In practice, this method was not being
used for that. Rather, setRef() existed as memory optimisation for PHP 4.
+* Passing false to ParserOptions::setWrapOutputClass() is deprecated. Use the
+ 'unwrap' transform to ParserOutput::getText() instead.
== Compatibility ==
MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
== Compatibility ==
MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
);
return $out instanceof ParserOutput
);
return $out instanceof ParserOutput
- ? $out->getText( [ 'enableSectionEditLinks' => false ] )
+ ? $out->getText( [
+ 'enableSectionEditLinks' => false,
+ // Wrapping messages in an extra <div> 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.
+ 'unwrap' => true,
+ ] )
$result_array['text'] = $p_result->getText( [
'allowTOC' => !$params['disabletoc'],
'enableSectionEditLinks' => !$params['disableeditsection'],
$result_array['text'] = $p_result->getText( [
'allowTOC' => !$params['disabletoc'],
'enableSectionEditLinks' => !$params['disableeditsection'],
+ 'unwrap' => $params['wrapoutputclass'] === '',
] );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
}
] );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
}
if ( $params['disabletidy'] ) {
$popts->setTidy( false );
}
if ( $params['disabletidy'] ) {
$popts->setTidy( false );
}
- $popts->setWrapOutputClass(
- $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
- );
+ if ( $params['wrapoutputclass'] !== '' ) {
+ $popts->setWrapOutputClass( $params['wrapoutputclass'] );
+ }
$reset = null;
$suppressCache = false;
$reset = null;
$suppressCache = false;
$po = ParserOptions::newFromAnon();
$po->setEditSection( false );
$po->setAllowUnsafeRawHtml( false );
$po = ParserOptions::newFromAnon();
$po->setEditSection( false );
$po->setAllowUnsafeRawHtml( false );
- $po->setWrapOutputClass( false );
// from malicious sources. As a precaution, disable
// the <html> parser tag when parsing messages.
$this->mParserOptions->setAllowUnsafeRawHtml( false );
// from malicious sources. As a precaution, disable
// the <html> parser tag when parsing messages.
$this->mParserOptions->setAllowUnsafeRawHtml( false );
- // Wrapping messages in an extra <div> 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;
}
return $this->mParserOptions;
$this->parserTitle = Title::newFromText( 'Installer' );
$this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :(
$this->parserOptions->setEditSection( false );
$this->parserTitle = Title::newFromText( 'Installer' );
$this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :(
$this->parserOptions->setEditSection( false );
- $this->parserOptions->setWrapOutputClass( false );
// Don't try to access DB before user language is initialised
$this->setParserLanguage( Language::factory( 'en' ) );
}
// Don't try to access DB before user language is initialised
$this->setParserLanguage( Language::factory( 'en' ) );
}
$out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
$html = $out->getText( [
'enableSectionEditLinks' => false,
$out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
$html = $out->getText( [
'enableSectionEditLinks' => false,
] );
} catch ( MediaWiki\Services\ServiceDisabledException $e ) {
$html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
] );
} catch ( MediaWiki\Services\ServiceDisabledException $e ) {
$html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
* CSS class to use to wrap output from Parser::parse()
* @since 1.30
* @param string|bool $className Set false to disable wrapping.
* CSS class to use to wrap output from Parser::parse()
* @since 1.30
* @param string|bool $className Set false to disable wrapping.
+ * Passing false is deprecated since MediaWiki 1.31
* @return string|bool Current value
*/
public function setWrapOutputClass( $className ) {
* @return string|bool Current value
*/
public function setWrapOutputClass( $className ) {
*/
class ParserOutput extends CacheTime {
/**
*/
class ParserOutput extends CacheTime {
/**
- * Feature flag to indicate to extensions that MediaWiki core supports and
+ * Feature flags to indicate to extensions that MediaWiki core supports and
* uses getText() stateless transforms.
*/
const SUPPORTS_STATELESS_TRANSFORMS = 1;
* uses getText() stateless transforms.
*/
const SUPPORTS_STATELESS_TRANSFORMS = 1;
+ const SUPPORTS_UNWRAP_TRANSFORM = 1;
/**
* @var string $mText The output text
/**
* @var string $mText The output text
* to generate one and `__NOTOC__` wasn't used. Default is true,
* but might be statefully overridden.
* - enableSectionEditLinks: (bool) Include section edit links, assuming
* to generate one and `__NOTOC__` wasn't used. Default is true,
* but might be statefully overridden.
* - enableSectionEditLinks: (bool) Include section edit links, assuming
- * section edit link tokens are present in the HTML. Default is true,
+ * section edit link tokens are present in the HTML. Default is true,
* but might be statefully overridden.
* but might be statefully overridden.
+ * - unwrap: (bool) Remove a wrapping mw-parser-output div. Default is false.
* @return string HTML
*/
public function getText( $options = [] ) {
* @return string HTML
*/
public function getText( $options = [] ) {
// In that situation, the historical behavior (possibly buggy) is to remove the TOC.
'allowTOC' => !empty( $this->mTOCEnabled ),
'enableSectionEditLinks' => $this->mEditSectionTokens,
// In that situation, the historical behavior (possibly buggy) is to remove the TOC.
'allowTOC' => !empty( $this->mTOCEnabled ),
'enableSectionEditLinks' => $this->mEditSectionTokens,
];
$text = $this->mText;
Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
];
$text = $this->mText;
Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
+ if ( $options['unwrap'] !== false ) {
+ $start = Html::openElement( 'div', [
+ 'class' => 'mw-parser-output'
+ ] );
+ $startLen = strlen( $start );
+ $end = Html::closeElement( 'div' );
+ $endLen = strlen( $end );
+
+ if ( substr( $text, 0, $startLen ) === $start && substr( $text, -$endLen ) === $end ) {
+ $text = substr( $text, $startLen, -$endLen );
+ }
+ }
+
if ( $options['enableSectionEditLinks'] ) {
$text = preg_replace_callback(
self::EDITSECTION_REGEX,
if ( $options['enableSectionEditLinks'] ) {
$text = preg_replace_callback(
self::EDITSECTION_REGEX,
$options = ParserOptions::newFromContext( $context );
$options->setTimestamp( $this->getFakeTimestamp() );
$options = ParserOptions::newFromContext( $context );
$options->setTimestamp( $this->getFakeTimestamp() );
- if ( !isset( $opts['wrap'] ) ) {
- $options->setWrapOutputClass( false );
- }
-
if ( isset( $opts['tidy'] ) ) {
if ( !$this->tidySupport->isEnabled() ) {
$this->recorder->skipped( $test, 'tidy extension is not installed' );
if ( isset( $opts['tidy'] ) ) {
if ( !$this->tidySupport->isEnabled() ) {
$this->recorder->skipped( $test, 'tidy extension is not installed' );
} else {
$output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
$out = $output->getText( [
} else {
$output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
$out = $output->getText( [
- 'allowTOC' => !isset( $opts['notoc'] )
+ 'allowTOC' => !isset( $opts['notoc'] ),
+ 'unwrap' => !isset( $opts['wrap'] ),
] );
if ( isset( $opts['tidy'] ) ) {
$out = preg_replace( '/\s+$/', '', $out );
] );
if ( isset( $opts['tidy'] ) ) {
$out = preg_replace( '/\s+$/', '', $out );
// FIXME: This test should pass without setting global content language
$this->options = ParserOptions::newFromUserAndLang( new User, $contLang );
$this->options->setTemplateCallback( [ __CLASS__, 'statelessFetchTemplate' ] );
// 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();
$this->parser = new Parser;
MagicWord::clearCache();
$title = Title::newFromText( 'Unit test' );
$options = ParserOptions::newFromUser( new User() );
$title = Title::newFromText( 'Unit test' );
$options = ParserOptions::newFromUser( new User() );
- $options->setWrapOutputClass( false );
$this->assertEquals( "<p>$longLine</p>",
$this->assertEquals( "<p>$longLine</p>",
- $this->parser->parse( $longLine, $title, $options )->getText() );
+ $this->parser->parse( $longLine, $title, $options )->getText( [ 'unwrap' => true ] ) );
$parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options );
$this->assertEquals(
"<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>",
$parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options );
$this->assertEquals(
"<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>",
- $parserOutput->getText()
+ $parserOutput->getText( [ 'unwrap' => true ] )
'No overrides' => [ true, [] ],
'In-key options are ok' => [ true, [
'thumbsize' => 1e100,
'No overrides' => [ true, [] ],
'In-key options are ok' => [ true, [
'thumbsize' => 1e100,
] ],
'Non-in-key options are not ok' => [ false, [
'removeComments' => false,
] ],
'Non-in-key options are not ok' => [ false, [
'removeComments' => false,
}
public static function provideOptionsHash() {
}
public static function provideOptionsHash() {
- $used = [ 'wrapclass', 'printable' ];
+ $used = [ 'thumbsize', 'printable' ];
$classWrapper = TestingAccessWrapper::newFromClass( ParserOptions::class );
$classWrapper->getDefaults();
$classWrapper = TestingAccessWrapper::newFromClass( ParserOptions::class );
$classWrapper->getDefaults();
'Canonical options, used some options' => [ $used, 'canonical', [] ],
'Used some options, non-default values' => [
$used,
'Canonical options, used some options' => [ $used, 'canonical', [] ],
'Used some options, non-default values' => [
$used,
- 'printable=1!wrapclass=foobar',
+ 'printable=1!thumbsize=200',
- 'wrapclass' => 'foobar',
'printable' => true,
]
],
'printable' => true,
]
],
public static function provideGetText() {
// phpcs:disable Generic.Files.LineLength
$text = <<<EOF
public static function provideGetText() {
// phpcs:disable Generic.Files.LineLength
$text = <<<EOF
+<div class="mw-parser-output"><p>Test document.
</p>
<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
<p>Three
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
<p>Three
EOF;
return [
'No stateless options, default state' => [
[], [], $text, <<<EOF
EOF;
return [
'No stateless options, default state' => [
[], [], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
EOF
],
'No stateless options, TOC statefully disabled' => [
[], [ 'mTOCEnabled' => false ], $text, <<<EOF
EOF
],
'No stateless options, TOC statefully disabled' => [
[], [ 'mTOCEnabled' => false ], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
</p>
<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
</p>
<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
EOF
],
'No stateless options, section edits statefully disabled' => [
[], [ 'mEditSectionTokens' => false ], $text, <<<EOF
EOF
],
'No stateless options, section edits statefully disabled' => [
[], [ 'mEditSectionTokens' => false ], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
<p>Three
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
<p>Three
EOF
],
'Stateless options override stateful settings' => [
[ 'allowTOC' => true, 'enableSectionEditLinks' => true ],
[ 'mTOCEnabled' => false, 'mEditSectionTokens' => false ],
$text, <<<EOF
EOF
],
'Stateless options override stateful settings' => [
[ 'allowTOC' => true, 'enableSectionEditLinks' => true ],
[ 'mTOCEnabled' => false, 'mEditSectionTokens' => false ],
$text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
EOF
],
'Statelessly disable section edit links' => [
[ 'enableSectionEditLinks' => false ], [], $text, <<<EOF
EOF
],
'Statelessly disable section edit links' => [
[ 'enableSectionEditLinks' => false ], [], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
<p>Three
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
<p>Three
EOF
],
'Statelessly disable TOC' => [
[ 'allowTOC' => false ], [], $text, <<<EOF
EOF
],
'Statelessly disable TOC' => [
[ 'allowTOC' => false ], [], $text, <<<EOF
+<div class="mw-parser-output"><p>Test document.
+</p>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p></div>
+EOF
+ ],
+ 'Statelessly unwrap text' => [
+ [ 'unwrap' => true ], [], $text, <<<EOF
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>One
<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>One
+ 'Unwrap without a mw-parser-output wrapper' => [
+ [ 'unwrap' => true ], [], '<div class="foobar">Content</div>', '<div class="foobar">Content</div>'
+ ],
private function getParserOptions() {
global $wgContLang;
$popt = ParserOptions::newFromUserAndLang( new User, $wgContLang );
private function getParserOptions() {
global $wgContLang;
$popt = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $popt->setWrapOutputClass( false );
Title::newFromText( 'Test' ),
$this->getParserOptions()
);
Title::newFromText( 'Test' ),
$this->getParserOptions()
);
- $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() );
+ $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText( [ 'unwrap' => true ] ) );
$parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
}
$parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
}
Title::newFromText( 'Test' ),
$this->getParserOptions()
);
Title::newFromText( 'Test' ),
$this->getParserOptions()
);
- $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() );
+ $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText( [ 'unwrap' => true ] ) );
$parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
}
$parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle
}