* @file
* @ingroup Parser
*/
+
class ParserOutput extends CacheTime {
+ /**
+ * Feature flags to indicate to extensions that MediaWiki core supports and
+ * uses getText() stateless transforms.
+ */
+ const SUPPORTS_STATELESS_TRANSFORMS = 1;
+ const SUPPORTS_UNWRAP_TRANSFORM = 1;
+
/**
* @var string $mText The output text
*/
*/
public $mSections = [];
- /**
- * @var bool $mEditSectionTokens prefix/suffix markers if edit sections were output as tokens.
- */
- public $mEditSectionTokens = false;
-
/**
* @var array $mProperties Name/value pairs to be cached in the DB.
*/
*/
public $mTimestamp;
- /**
- * @var bool $mTOCEnabled Whether TOC should be shown, can't override __NOTOC__.
- */
- public $mTOCEnabled = true;
-
/**
* @var bool $mEnableOOUI Whether OOUI should be enabled.
*/
return $this->mText;
}
- public function getText() {
+ /**
+ * Get the output HTML
+ *
+ * @param array $options (since 1.31) Transformations to apply to the HTML
+ * - allowTOC: (bool) Show the TOC, assuming there were enough headings
+ * 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,
+ * but might be statefully overridden.
+ * - unwrap: (bool) Remove a wrapping mw-parser-output div. Default is false.
+ * - deduplicateStyles: (bool) When true, which is the default, `<style>`
+ * tags with the `data-mw-deduplicate` attribute set are deduplicated by
+ * value of the attribute: all but the first will be replaced by `<link
+ * rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
+ * the scheme-specific-part of the href is the (percent-encoded) value
+ * of the `data-mw-deduplicate` attribute.
+ * @return string HTML
+ */
+ public function getText( $options = [] ) {
+ $options += [
+ 'allowTOC' => true,
+ 'enableSectionEditLinks' => true,
+ 'unwrap' => false,
+ 'deduplicateStyles' => true,
+ ];
$text = $this->mText;
- if ( $this->mEditSectionTokens ) {
+
+ 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' );
+ $endPos = strrpos( $text, $end );
+ $endLen = strlen( $end );
+
+ if ( substr( $text, 0, $startLen ) === $start && $endPos !== false
+ // if the closing div is followed by real content, bail out of unwrapping
+ && preg_match( '/^(?>\s*<!--.*?-->)*\s*$/s', substr( $text, $endPos + $endLen ) )
+ ) {
+ $text = substr( $text, $startLen );
+ $text = substr( $text, 0, $endPos - $startLen )
+ . substr( $text, $endPos - $startLen + $endLen );
+ }
+ }
+
+ if ( $options['enableSectionEditLinks'] ) {
$text = preg_replace_callback(
self::EDITSECTION_REGEX,
function ( $m ) {
$text = preg_replace( self::EDITSECTION_REGEX, '', $text );
}
- // If you have an old cached version of this class - sorry, you can't disable the TOC
- if ( isset( $this->mTOCEnabled ) && $this->mTOCEnabled ) {
+ if ( $options['allowTOC'] ) {
$text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
} else {
$text = preg_replace(
$text
);
}
+
+ if ( $options['deduplicateStyles'] ) {
+ $seen = [];
+ $text = preg_replace_callback(
+ '#<style\s+([^>]*data-mw-deduplicate\s*=[^>]*)>.*?</style>#s',
+ function ( $m ) use ( &$seen ) {
+ $attr = Sanitizer::decodeTagAttributes( $m[1] );
+ if ( !isset( $attr['data-mw-deduplicate'] ) ) {
+ return $m[0];
+ }
+
+ $key = $attr['data-mw-deduplicate'];
+ if ( !isset( $seen[$key] ) ) {
+ $seen[$key] = true;
+ return $m[0];
+ }
+
+ // We were going to use an empty <style> here, but there
+ // was concern that would be too much overhead for browsers.
+ // So let's hope a <link> with a non-standard rel and href isn't
+ // going to be misinterpreted or mangled by any subsequent processing.
+ return Html::element( 'link', [
+ 'rel' => 'mw-deduplicated-inline-style',
+ 'href' => "mw-data:" . wfUrlencode( $key ),
+ ] );
+ },
+ $text
+ );
+ }
+
return $text;
}
return $this->mSections;
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function getEditSectionTokens() {
- return $this->mEditSectionTokens;
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function &getLinks() {
return $this->mLimitReportJSData;
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function getTOCEnabled() {
- return $this->mTOCEnabled;
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function getEnableOOUI() {
return wfSetVar( $this->mSections, $toc );
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function setEditSectionTokens( $t ) {
- return wfSetVar( $this->mEditSectionTokens, $t );
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function setIndexPolicy( $policy ) {
return wfSetVar( $this->mTimestamp, $timestamp );
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function setTOCEnabled( $flag ) {
- return wfSetVar( $this->mTOCEnabled, $flag );
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function addCategory( $c, $sort ) {
# Replace unnecessary URL escape codes with the referenced character
# This prevents spammers from hiding links from the filters
- $url = parser::normalizeLinkUrl( $url );
+ $url = Parser::normalizeLinkUrl( $url );
$registerExternalLink = true;
if ( !$wgRegisterInternalExternals ) {