Add ParserOutputFlags::NO_TOC

Rather than suppress the TOCData in ParserOutput when __NOTOC__ is used,
set a new parser output flag, NO_TOC, since some clients want to know
whether there are sections present on the page irrespective of whether
the UX for the Table Of Contents should be displayed/suppressed.

Added OutputPage::getOutputFlag() as an @internal method for the
moment; eventually we should use the same object to represent
metadata in ParserOutput and OutputPage (T301020).

Bug: T332243
Followup-To: Ife2126ace95ac4d9ec44f6374c63d8fc995cf034
Followup-To: Iea6426336f93c053a5977768f0785cdb46daf5bf
Change-Id: Ib41e6e4926cb752826ad75d10e8692125fc0b064
This commit is contained in:
C. Scott Ananian 2023-03-21 14:26:05 -04:00
parent 0064e43837
commit 0448851e92
6 changed files with 143 additions and 30 deletions

View file

@ -377,6 +377,11 @@ class OutputPage extends ContextSource {
*/
private $mEnableTOC = false;
/**
* @var array<string,bool> Flags set in the ParserOutput
*/
private $mOutputFlags = [];
/**
* @var string|null The URL to send in a <link> element with rel=license
*/
@ -2071,6 +2076,16 @@ class OutputPage extends ContextSource {
return $this->tocData;
}
/**
* @internal Will be replaced by direct access to
* ParserOutput::getOutputFlag()
* @param string $name A flag name from ParserOutputFlags
* @return bool
*/
public function getOutputFlag( string $name ): bool {
return isset( $this->mOutputFlags[$name] );
}
/**
* Add all metadata associated with a ParserOutput object, but without the actual HTML. This
* includes categories, language links, ResourceLoader modules, effects of certain magic words,
@ -2106,6 +2121,8 @@ class OutputPage extends ContextSource {
// addParserOutputMetadata() may be called multiple times, but the
// following lines overwrite any previous data. These should
// be migrated to an injection pattern. (T301020, T300979)
// (Note that OutputPage::getOutputFlag() also contains this
// information, with flags from each $parserOutput all OR'ed together.)
$this->mNewSectionLink = $parserOutput->getNewSection();
$this->mHideNewSectionLink = $parserOutput->getHideNewSection();
$this->mNoGallery = $parserOutput->getNoGallery();
@ -2219,6 +2236,16 @@ class OutputPage extends ContextSource {
// Eventually we'll emit a deprecation message here (T293513)
$this->mEnableTOC = true;
}
// Uniform handling of all boolean flags: they are OR'ed together
// (See ParserOutput::collectMetadata())
$flags =
array_flip( $parserOutput->getAllFlags() ) +
array_flip( ParserOutputFlags::cases() );
foreach ( $flags as $name => $ignore ) {
if ( $parserOutput->getOutputFlag( $name ) ) {
$this->mOutputFlags[$name] = true;
}
}
}
/**
@ -4583,7 +4610,8 @@ class OutputPage extends ContextSource {
}
/**
* Whether the output has a table of contents
* Whether the output has a table of contents when the ToC is
* rendered inline.
* @return bool
* @since 1.22
*/

View file

@ -4418,16 +4418,7 @@ class Parser {
$enoughToc = false;
}
if ( $enoughToc ) {
// Record the fact that the TOC should be shown. T294950
// (We shouldn't be looking at ::getTOCHTML() for this because
// eventually that will be replaced (T293513) and
// $tocData will contain sections even if there aren't
// $enoughToc to show.)
$this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
}
if ( $isMain && !$suppressToc && $this->mShowToc ) {
if ( $isMain && !$suppressToc ) {
// We generally output the section information via the API
// even if there isn't "enough" of a ToC to merit showing
// it -- but the "suppress TOC" parser option is set when
@ -4435,10 +4426,26 @@ class Parser {
// (ie, JavaScript content that might have spurious === or
// <h2>: T307691) so we will *not* set section information
// in that case.
// The TOCData will also be null/unset if __NOTOC__ is
// used on the page (and not overridden by __TOC__ or
// __FORCETOC__).
$this->mOutput->setTOCData( $tocData );
// T294950: Record a suggestion that the TOC should be shown.
// We shouldn't be looking at ::getTOCHTML() for this because
// that was replaced (T293513); and $tocData will contain sections
// even if there aren't $enoughToc to show (T332243).
// Skins are free to ignore this suggestion and implement their
// own criteria for showing/suppressing TOC (T318186).
if ( $enoughToc ) {
$this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
}
// If __NOTOC__ is used on the page (and not overridden by
// __TOC__ or __FORCETOC__) set the NO_TOC flag to tell
// the skin that although the section information is
// valid, it should perhaps not be presented as a Table Of
// Contents.
if ( !$this->mShowToc ) {
$this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
}
}
# split up and insert constructed headlines

View file

@ -94,10 +94,22 @@ class ParserOutputFlags {
// These flags are stored in the ParserOutput::$mFlags array
/**
* @var string Show the table of contents in the skin?
* @var string Show the table of contents in the skin? This is
* a /suggestion/ based on whether the TOC is "large enough"
* and other factors, and is intended mostly for skins which
* want to match the behavior of the traditional inline ToC.
*/
public const SHOW_TOC = 'show-toc';
/**
* @var string Suppress the table of contents in the skin?
* This reflects the use of the __NOTOC__ magic word in the
* article (possibly modified by __TOC__ or __FORCETOC__),
* and represents an explicit request from the author to
* hide the TOC.
*/
public const NO_TOC = 'no-toc';
/**
* @var string
*/
@ -161,6 +173,7 @@ class ParserOutputFlags {
self::NEW_SECTION,
self::HIDE_NEW_SECTION,
self::SHOW_TOC,
self::NO_TOC,
self::PREVENT_CLICKJACKING,
self::VARY_REVISION,
self::VARY_REVISION_ID,

View file

@ -18,6 +18,7 @@
namespace MediaWiki\Skin;
use MediaWiki\Parser\ParserOutputFlags;
use OutputPage;
/**
@ -83,6 +84,10 @@ class SkinComponentTableOfContents implements SkinComponent {
if ( $tocData === null ) {
return [];
}
// Respect __NOTOC__
if ( $this->output->getOutputFlag( ParserOutputFlags::NO_TOC ) ) {
return [];
}
$outputSections = $tocData->getSections();

View file

@ -881,7 +881,7 @@ __NOTOC__
{{sections}}
==Section 4==
!! metadata
flags=
flags=no-toc
!! html/php
<h2><span class="mw-headline" id="Section_0">Section 0</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section 0">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<h3><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Template:Sections&amp;action=edit&amp;section=T-1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
@ -934,7 +934,7 @@ showflags
!! wikitext
__NOTOC__
!! metadata
flags=
flags=no-toc
!! html/php
!! html/parsoid
<meta property="mw:PageProp/notoc"/>
@ -1005,7 +1005,7 @@ showflags
__NOTOC__
== One ==
!! metadata
flags=
flags=no-toc
!! html/php
<h2><span class="mw-headline" id="One">One</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: One">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! html/parsoid
@ -1102,7 +1102,7 @@ __NOTOC__
== Three ==
=== Four ===
!! metadata
flags=
flags=no-toc
!! html/php
<h2><span class="mw-headline" id="One">One</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: One">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<h3><span class="mw-headline" id="Two">Two</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Two">edit</a><span class="mw-editsection-bracket">]</span></span></h3>

View file

@ -1174,14 +1174,20 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
$op = $this->newInstance();
$this->assertFalse( $op->showNewSectionLink() );
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::NEW_SECTION ) );
$pOut1 = $this->createParserOutputStub( 'getNewSection', true );
$pOut1 = $this->createParserOutputStubWithFlags(
[ 'getNewSection' => true ], [ ParserOutputFlags::NEW_SECTION ]
);
$op->addParserOutputMetadata( $pOut1 );
$this->assertTrue( $op->showNewSectionLink() );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NEW_SECTION ) );
$pOut2 = $this->createParserOutputStub( 'getNewSection', false );
$op->addParserOutput( $pOut2 );
$this->assertFalse( $op->showNewSectionLink() );
// Note that flags are OR'ed together, and not reset.
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NEW_SECTION ) );
}
/**
@ -1193,14 +1199,20 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
$op = $this->newInstance();
$this->assertFalse( $op->forceHideNewSectionLink() );
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION ) );
$pOut1 = $this->createParserOutputStub( 'getHideNewSection', true );
$pOut1 = $this->createParserOutputStubWithFlags(
[ 'getHideNewSection' => true ], [ ParserOutputFlags::HIDE_NEW_SECTION ]
);
$op->addParserOutputMetadata( $pOut1 );
$this->assertTrue( $op->forceHideNewSectionLink() );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION ) );
$pOut2 = $this->createParserOutputStub( 'getHideNewSection', false );
$op->addParserOutput( $pOut2 );
$this->assertFalse( $op->forceHideNewSectionLink() );
// Note that flags are OR'ed together, and not reset.
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION ) );
}
/**
@ -1762,6 +1774,19 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
} elseif ( count( $args ) === 2 ) {
$retVals = [ $args[0] => $args[1] ];
}
return $this->createParserOutputStubWithFlags( $retVals, [] );
}
/**
* First argument is an array
* [ $methodName => $returnValue, $methodName => $returnValue, ... ]
* Second argument is an array of parser flags for which ::getOutputFlag()
* should return 'TRUE'.
* @param array $retVals
* @param array $flags
* @return ParserOutput
*/
private function createParserOutputStubWithFlags( array $retVals, array $flags ): ParserOutput {
$pOut = $this->createMock( ParserOutput::class );
foreach ( $retVals as $method => $retVal ) {
$pOut->method( $method )->willReturn( $retVal );
@ -1786,6 +1811,10 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
$pOut->method( $method )->willReturn( [] );
}
$pOut->method( 'getOutputFlag' )->willReturnCallback( static function ( $name ) use ( $flags ) {
return in_array( $name, $flags, true );
} );
return $pOut;
}
@ -2042,14 +2071,20 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
public function testNoGallery() {
$op = $this->newInstance();
$this->assertFalse( $op->mNoGallery );
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::NO_GALLERY ) );
$stubPO1 = $this->createParserOutputStub( 'getNoGallery', true );
$stubPO1 = $this->createParserOutputStubWithFlags(
[ 'getNoGallery' => true ], [ ParserOutputFlags::NO_GALLERY ]
);
$op->addParserOutputMetadata( $stubPO1 );
$this->assertTrue( $op->mNoGallery );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NO_GALLERY ) );
$stubPO2 = $this->createParserOutputStub( 'getNoGallery', false );
$op->addParserOutput( $stubPO2 );
$this->assertFalse( $op->mNoGallery );
// Note that flags are OR'ed together, and not reset.
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NO_GALLERY ) );
}
private static $parserOutputHookCalled;
@ -2136,15 +2171,19 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
$op = $this->newInstance();
$this->assertSame( '', $op->getHTML() );
$this->assertFalse( $op->showNewSectionLink() );
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::NEW_SECTION ) );
$pOut = $this->createParserOutputStub( [
$pOut = $this->createParserOutputStubWithFlags( [
'getText' => '<some text>',
'getNewSection' => true,
], [
ParserOutputFlags::NEW_SECTION,
] );
$op->addParserOutput( $pOut );
$this->assertSame( '<some text>', $op->getHTML() );
$this->assertTrue( $op->showNewSectionLink() );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NEW_SECTION ) );
}
/**
@ -3162,24 +3201,24 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
public function testIsTOCEnabled() {
$op = $this->newInstance();
$this->assertFalse( $op->isTOCEnabled() );
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::SHOW_TOC ) );
$pOut1 = $this->createParserOutputStub();
$pOut1->method( 'getOutputFlag' )->willReturnMap( [
[ ParserOutputFlags::SHOW_TOC, false ],
] );
$op->addParserOutputMetadata( $pOut1 );
$this->assertFalse( $op->isTOCEnabled() );
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::SHOW_TOC ) );
$pOut2 = $this->createParserOutputStub();
$pOut2->method( 'getOutputFlag' )->willReturnMap( [
[ ParserOutputFlags::SHOW_TOC, true ],
] );
$pOut2 = $this->createParserOutputStubWithFlags(
[], [ ParserOutputFlags::SHOW_TOC ]
);
$op->addParserOutput( $pOut2 );
$this->assertTrue( $op->isTOCEnabled() );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::SHOW_TOC ) );
// The parser output doesn't disable the TOC after it was enabled
$op->addParserOutputMetadata( $pOut1 );
$this->assertTrue( $op->isTOCEnabled() );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::SHOW_TOC ) );
}
/**
@ -3213,6 +3252,27 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
$this->assertTrue( $op->isTOCEnabled() );
}
/**
* @covers OutputPage::addParserOutputMetadata
* @covers OutputPage::addParserOutput
*/
public function testNoTOC() {
$op = $this->newInstance();
$this->assertFalse( $op->getOutputFlag( ParserOutputFlags::NO_TOC ) );
$stubPO1 = $this->createParserOutputStubWithFlags(
[], [ ParserOutputFlags::NO_TOC ]
);
$op->addParserOutputMetadata( $stubPO1 );
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NO_TOC ) );
$stubPO2 = $this->createParserOutputStub();
$this->assertFalse( $stubPO2->getOutputFlag( ParserOutputFlags::NO_TOC ) );
$op->addParserOutput( $stubPO2 );
// Note that flags are OR'ed together, and not reset.
$this->assertTrue( $op->getOutputFlag( ParserOutputFlags::NO_TOC ) );
}
/**
* @dataProvider providePreloadLinkHeaders
* @covers \MediaWiki\ResourceLoader\SkinModule