Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.63% covered (warning)
83.63%
812 / 971
70.39% covered (warning)
70.39%
107 / 152
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParserOutput
83.71% covered (warning)
83.71%
812 / 970
70.39% covered (warning)
70.39%
107 / 152
1145.59
0.00% covered (danger)
0.00%
0 / 1
 __construct
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 getContentHolder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setContentHolder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRawText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 runOutputPipeline
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 addCacheMessage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addWrapperDivClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearWrapperDivClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWrapperDivClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSpeculativeRevIdUsed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSpeculativeRevIdUsed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSpeculativePageIdUsed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSpeculativePageIdUsed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRevisionTimestampUsed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRevisionTimestampUsed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRevisionUsedSha1Base36
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getRevisionUsedSha1Base36
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLanguageLinks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getLanguageLinksInternal
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getInterwikiLinks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getCategoryNames
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCategoryMap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCategorySortKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIndicators
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTitleText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTOCData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCacheMessage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSections
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getLinkList
86.42% covered (warning)
86.42%
70 / 81
0.00% covered (danger)
0.00%
0 / 1
39.25
 appendLinkList
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
2
 getLinks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasLinks
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getLinksSpecial
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTemplates
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTemplateIds
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getImages
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasImages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFileSearchOptions
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getExternalLinks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setNoGallery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNoGallery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeadItems
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getModules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getModuleStyles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getJsConfigVars
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 getWarnings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWarningMsgs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIndexPolicy
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getRevisionTimestamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTimestamp
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getLimitReportData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLimitReportJSData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEnableOOUI
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtraCSPDefaultSrcs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtraCSPScriptSrcs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtraCSPStyleSrcs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRawText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setText
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setLanguageLinks
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 clearLanguageLinks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTitleText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTOCData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSections
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setIndexPolicy
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 setRevisionTimestamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTimestamp
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addCategory
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setCategories
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIndicator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setEnableOOUI
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addLanguageLink
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 addWarningMsgVal
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
2.00
 addWarningMsg
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setNewSection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setHideNewSection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHideNewSection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNewSection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isLinkInternal
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 addExternalLink
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 addLink
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 addImage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 addTemplate
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 addInterwikiLink
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 addExistenceDependency
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 addHeadItem
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 addModules
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addModuleStyles
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addJsConfigVars
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 setJsConfigVar
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 appendJsConfigVar
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 addOutputPageMetadata
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 setDisplayTitle
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayTitle
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getLanguage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRedirectHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRedirectHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRenderId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRenderId
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getAllFlags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPageProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setNumericPageProperty
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 setUnsortedPageProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unsetPageProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageProperties
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOutputFlag
84.85% covered (warning)
84.85%
28 / 33
0.00% covered (danger)
0.00%
0 / 1
12.50
 getOutputFlag
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
10
 appendOutputStrings
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
2
 getOutputStrings
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 setExtensionData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 appendExtensionData
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 getExtensionData
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getTimes
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 resetParseStartTime
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 clearParseStartTime
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 recordTimeProfile
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 getTimeProfile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLimitReportData
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 hasReducedExpiry
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getCacheExpiry
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 setPreventClickjacking
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPreventClickjacking
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updateRuntimeAdaptiveExpiry
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addExtraCSPDefaultSrc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addExtraCSPStyleSrc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addExtraCSPScriptSrc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 finalizeAdaptiveCacheExpiry
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 setFromParserOptions
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
7.03
 mergeInternalMetaDataFrom
70.73% covered (warning)
70.73%
29 / 41
0.00% covered (danger)
0.00%
0 / 1
22.42
 mergeHtmlMetaDataFrom
93.48% covered (success)
93.48%
43 / 46
0.00% covered (danger)
0.00%
0 / 1
15.06
 mergeTrackingMetaDataFrom
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 collectMetadata
73.86% covered (warning)
73.86%
65 / 88
0.00% covered (danger)
0.00%
0 / 1
63.78
 mergeMixedList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mergeList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mergeMap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeMapStrategy
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 collectMapStrategy
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 mergeMapStrategy
84.62% covered (warning)
84.62%
22 / 26
0.00% covered (danger)
0.00%
0 / 1
14.71
 useEachMinValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 useEachTotalValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 useMaxValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 toJsonArray
92.59% covered (success)
92.59%
50 / 54
0.00% covered (danger)
0.00%
0 / 1
4.01
 newFromJsonArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 initFromJson
82.50% covered (warning)
82.50%
66 / 80
0.00% covered (danger)
0.00%
0 / 1
19.74
 detectAndEncodeBinary
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 detectAndDecodeBinary
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 __serialize
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 __clone
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getContentHolderText
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setContentHolderText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare( strict_types = 1 );
3
4/**
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Parser;
10
11use InvalidArgumentException;
12use LogicException;
13use MediaWiki\Edit\ParsoidRenderID;
14use MediaWiki\Json\JsonDeserializable;
15use MediaWiki\MainConfigNames;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Message\Message;
18use MediaWiki\Output\OutputPage;
19use MediaWiki\Title\TitleValue;
20use UnhandledMatchError;
21use Wikimedia\Assert\Assert;
22use Wikimedia\Bcp47Code\Bcp47Code;
23use Wikimedia\Bcp47Code\Bcp47CodeValue;
24use Wikimedia\Message\MessageSpecifier;
25use Wikimedia\Message\MessageValue;
26use Wikimedia\Parsoid\Core\ContentMetadataCollector;
27use Wikimedia\Parsoid\Core\ContentMetadataCollectorCompat;
28use Wikimedia\Parsoid\Core\HtmlPageBundle;
29use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
30use Wikimedia\Parsoid\Core\MergeStrategy;
31use Wikimedia\Parsoid\Core\TOCData;
32
33/**
34 * ParserOutput is a rendering of a Content object or a message.
35 * Content objects and messages often contain wikitext, but not always.
36 *
37 * `ParserOutput` object combine the HTML rendering of Content objects
38 * or messages, available via `::getContentHolderText()`, with various bits of
39 * metadata generated during rendering, which may include categories,
40 * links, page properties, and extension data, among others.
41 *
42 * `ParserOutput` objects corresponding to the content of page revisions
43 * are created by the `ParserOutputAccess` service, which
44 * automatically caches them via `ParserCache` where appropriate and
45 * produces new output via `ContentHandler` as needed.
46 *
47 * In addition, wikitext from system messages as well as odd bits of
48 * wikitext rendered to create special pages and other UX elements are
49 * rendered to `ParserOutput` objects.  In these cases the metadata
50 * from the `ParserOutput` is generally discarded and the
51 * `ParserOutput` is not cached.  `ParserOptions::setIsMessage(true)`
52 * is usually used when rendering system messages.
53 * `ParserOptions::setInterfaceMessage(true)` is usually used when
54 * rendering system messages in the user interface language,
55 * and occasionally for the other odd bits of wikitext as well.
56 * These options are not used as consistently as one would hope.
57 *
58 * A `ParserOutput` object corresponding to a given revision may be a
59 * combination of the renderings of multiple "slots":
60 * the Multi-Content Revisions (MCR) work allows articles to be
61 * composed from multiple `Content` objects.  Each `Content` renders
62 * to a `ParserOutput`, and those `ParserOutput`s are merged by
63 * `RevisionRenderer::combineSlotOutput()` to create the final article
64 * output.
65 *
66 * Similarly, `OutputPage` maintains metadata overlapping
67 * with the metadata kept by `ParserOutput` (T301020) and may merge
68 * several `ParserOutput`s using `OutputPage::addParserOutput()` to
69 * create the final output page.  Parsoid parses certain transclusions
70 * in independent top-level contexts using
71 * `Parser::parseExtensionTagAsTopLevelDoc()` and these also result in
72 * `ParserOutput`s which are merged via
73 * `ParserOutput::collectMetadata()`.
74 *
75 * Future plans for incremental parsing and asynchronous rendering may
76 * result in several of these component `ParserOutput` objects being
77 * cached independently and then recombined asynchronously, so
78 * operations on `ParserOutput` objects should be compatible with that
79 * model (T300979).
80 *
81 * @ingroup Parser
82 */
83class ParserOutput extends CacheTime implements ContentMetadataCollector {
84    // This is used to break cyclic dependencies and allow a measure
85    // of compatibility when new methods are added to ContentMetadataCollector
86    // by Parsoid.
87    use ContentMetadataCollectorCompat;
88
89    /**
90     * @internal
91     * @since 1.45
92     */
93    public const PARSOID_PAGE_BUNDLE_KEY = 'parsoid-page-bundle';
94
95    /**
96     * @internal
97     * @since 1.38
98     */
99    public const MW_MERGE_STRATEGY_KEY = '_mw-strategy';
100
101    /**
102     * Merge strategy to use for ParserOutput accumulators: "union"
103     * means that values are strings, stored as a set, and exposed as
104     * a PHP associative array mapping from values to `true`.
105     *
106     * This constant should be treated as @internal until we expose
107     * alternative merge strategies for external use.
108     * @internal
109     * @since 1.38
110     * @deprecated since 1.45; use MergeStrategy::UNION
111     */
112    public const MW_MERGE_STRATEGY_UNION = MergeStrategy::UNION;
113
114    private ContentHolder $contentHolder;
115
116    /**
117     * @var array<string,string> Array mapping interwiki prefix to (non DB key) Titles (e.g. 'fr' => 'Test page')
118     */
119    private array $mLanguageLinkMap = [];
120
121    /**
122     * @var array<string,string> Map of category names to sort keys
123     */
124    private array $mCategories = [];
125
126    /**
127     * @var array<string,string> Page status indicators, usually displayed in top-right corner.
128     */
129    private array $mIndicators = [];
130
131    /**
132     * @var string Title text of the chosen language variant, as HTML.
133     */
134    private string $mTitleText;
135
136    /**
137     * @var array<int,array<string,int>> 2-D map of NS/DBK to ID for the links in the document.
138     *  ID=zero for broken.
139     */
140    private array $mLinks = [];
141
142    /**
143     * @var array<string,int> Keys are DBKs for the links to special pages in the document.
144     * @since 1.35
145     */
146    private array $mLinksSpecial = [];
147
148    /**
149     * @var array<int,array<string,int>> 2-D map of NS/DBK to ID for the template references.
150     *  ID=zero for broken.
151     */
152    private array $mTemplates = [];
153
154    /**
155     * @var array<int,array<string,int>> 2-D map of NS/DBK to rev ID for the template references.
156     *  ID=zero for broken.
157     */
158    private array $mTemplateIds = [];
159
160    /**
161     * @var array<string,int> DB keys of the images used, in the array key only
162     */
163    private array $mImages = [];
164
165    /**
166     * @var array<string,array<string,string>> DB keys of the images used mapped to sha1 and MW timestamp.
167     */
168    private array $mFileSearchOptions = [];
169
170    /**
171     * @var array<string,int> External link URLs, in the key only.
172     */
173    private array $mExternalLinks = [];
174
175    /**
176     * @var array<string,array<string,int>> 2-D map of prefix/DBK (in keys only)
177     *  for the inline interwiki links in the document.
178     */
179    private array $mInterwikiLinks = [];
180
181    /**
182     * @var array<int,array<string,bool>> 2-D map of NS/DBK to true for #ifexist and similar
183     */
184    private array $existenceLinks = [];
185
186    /**
187     * @var bool Show a new section link?
188     */
189    private bool $mNewSection = false;
190
191    /**
192     * @var bool Hide the new section link?
193     */
194    private bool $mHideNewSection = false;
195
196    /**
197     * @var bool No gallery on category page? (__NOGALLERY__).
198     */
199    private bool $mNoGallery = false;
200
201    /**
202     * @var array<string|int,string> Items to put in the <head> section
203     */
204    private array $mHeadItems = [];
205
206    /**
207     * @var array<string,true> Modules to be loaded by ResourceLoader
208     */
209    private array $mModuleSet = [];
210
211    /**
212     * @var array<string,true> Modules of which only the CSS will be loaded by ResourceLoader.
213     */
214    private array $mModuleStyleSet = [];
215
216    /**
217     * @var array JavaScript config variable for mw.config combined with this page.
218     */
219    private array $mJsConfigVars = [];
220
221    /**
222     * @var array<string,int> Warning text to be returned to the user.
223     *  Wikitext formatted, in the key only.
224     * @deprecated since 1.45; use ::$mWarningMsgs instead
225     */
226    private array $mWarnings = [];
227
228    /**
229     * @var array<string,MessageValue> *Unformatted* warning messages and
230     * arguments to be returned to the user.
231     */
232    private array $mWarningMsgs = [];
233
234    /**
235     * @var ?TOCData Table of contents data, or null if it hasn't been set.
236     */
237    private ?TOCData $mTOCData = null;
238
239    /**
240     * @var array<string,int|float|string> Name/value pairs to be cached in the DB.
241     */
242    private array $mProperties = [];
243
244    /**
245     * @var ?string Timestamp of the revision.
246     */
247    private ?string $mTimestamp = null;
248
249    /**
250     * @var bool Whether OOUI should be enabled.
251     */
252    private bool $mEnableOOUI = false;
253
254    /**
255     * @var bool Whether the index policy has been set to 'index'.
256     */
257    private bool $mIndexSet = false;
258
259    /**
260     * @var bool Whether the index policy has been set to 'noindex'.
261     */
262    private bool $mNoIndexSet = false;
263
264    /**
265     * @var array<string,mixed> extra data used by extensions.
266     */
267    private array $mExtensionData = [];
268
269    /**
270     * @var array Parser limit report data.
271     */
272    private array $mLimitReportData = [];
273
274    /** @var array Parser limit report data for JSON */
275    private array $mLimitReportJSData = [];
276
277    /** @var string Debug message added by ParserCache */
278    private string $mCacheMessage = '';
279
280    /**
281     * @var array Timestamps for getTimeProfile().
282     */
283    private array $mParseStartTime = [];
284
285    /**
286     * @var array Durations for getTimeProfile().
287     */
288    private array $mTimeProfile = [];
289
290    /**
291     * @var bool Whether to emit X-Frame-Options: DENY.
292     * This controls if anti-clickjacking / frame-breaking headers will
293     * be sent. This should be done for pages where edit actions are possible.
294     */
295    private bool $mPreventClickjacking = false;
296
297    /**
298     * @var list<string> Extra script-src for CSP
299     */
300    private array $mExtraScriptSrcs = [];
301
302    /**
303     * @var list<string> Extra default-src for CSP [Everything but script and style]
304     */
305    private array $mExtraDefaultSrcs = [];
306
307    /**
308     * @var list<string> Extra style-src for CSP
309     */
310    private array $mExtraStyleSrcs = [];
311
312    /**
313     * @var array<string,true> Generic flags.
314     */
315    private $mFlags = [];
316
317    private const SPECULATIVE_FIELDS = [
318        'speculativePageIdUsed',
319        'mSpeculativeRevId',
320        'revisionTimestampUsed',
321    ];
322
323    /** @var int|null Assumed rev ID for {{REVISIONID}} if no revision is set */
324    private ?int $mSpeculativeRevId = null;
325    /** @var int|null Assumed page ID for {{PAGEID}} if no revision is set */
326    private ?int $speculativePageIdUsed = null;
327    /** @var string|null Assumed rev timestamp for {{REVISIONTIMESTAMP}} if no revision is set */
328    private ?string $revisionTimestampUsed = null;
329
330    /** @var string|null SHA-1 base 36 hash of any self-transclusion */
331    private ?string $revisionUsedSha1Base36 = null;
332
333    /** string CSS classes to use for the wrapping div, stored in the array keys.
334     * If no class is given, no wrapper is added.
335     * @var array<string,true>
336     */
337    private array $mWrapperDivClasses = [];
338
339    /**
340     * @var ?int Upper bound of expiry based on parse duration;
341     *    null means "infinite" or "not set"
342     */
343    private ?int $mMaxAdaptiveExpiry = null;
344
345    // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
346    // Current values imply that m=3933.333333 and b=-333.333333
347    // See https://www.nngroup.com/articles/website-response-times/
348    private const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
349    private const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
350    private const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
351    private const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
352    private const MIN_AR_TTL = 15; // min adaptive TTL (for pool counter, and edit stashing)
353
354    /**
355     * @param string|null $text HTML. Use null to indicate that this ParserOutput contains only
356     *        meta-data, and the HTML output is undetermined, as opposed to empty. Passing null
357     *        here causes hasText() to return false. In 1.39 the default value changed from ''
358     *        to null.
359     * @param array $languageLinks
360     * @param array $categoryLinks
361     * @param bool $unused
362     * @param string $titletext
363     */
364    public function __construct( ?string $text = null, array $languageLinks = [], array $categoryLinks = [],
365        $unused = false, string $titletext = ''
366    ) {
367        if ( $text === null ) {
368            $this->contentHolder = ContentHolder::createEmpty();
369        } else {
370            $this->contentHolder = ContentHolder::createFromLegacyString( $text );
371        }
372        $this->mCategories = $categoryLinks;
373        $this->mTitleText = $titletext;
374        foreach ( $languageLinks as $ll ) {
375            $this->addLanguageLink( $ll );
376        }
377        // If the content handler does not specify an alternative (by
378        // calling ::resetParseStartTime() at a later point) then use
379        // the creation of the ParserOutput as the "start of parse" time.
380        $this->resetParseStartTime();
381    }
382
383    /**
384     * Return the ContentHolder storing the HTML/DOM contents of this
385     * ParserOutput.
386     * @unstable
387     */
388    public function getContentHolder(): ContentHolder {
389        return $this->contentHolder;
390    }
391
392    /**
393     * @internal Use __construct or PageBundleParserOutputConverter.
394     */
395    public function setContentHolder( ContentHolder $contentHolder ) {
396        $this->contentHolder = $contentHolder;
397    }
398
399    /**
400     * Returns true if text was passed to the constructor, or set using setText(). Returns false
401     * if null was passed to the $text parameter of the constructor to indicate that this
402     * ParserOutput only contains meta-data, and the HTML output is undetermined.
403     *
404     * @since 1.32
405     *
406     * @return bool Whether this ParserOutput contains rendered text. If this returns false, the
407     *         ParserOutput contains meta-data only.
408     */
409    public function hasText(): bool {
410        return $this->contentHolder->has( ContentHolder::BODY_FRAGMENT );
411    }
412
413    /**
414     * Get the cacheable text with <mw:editsection> markers still in it. The
415     * return value is suitable for writing back via setText() but is not valid
416     * for display to the user.
417     *
418     * @return string
419     * @since 1.27
420     * @deprecated since 1.45; use ::getContentHolderText() instead
421     */
422    public function getRawText() {
423        return $this->getContentHolderText();
424    }
425
426    /*
427     * @unstable This method is transitional and will be replaced by a method
428     * in another class, maybe ContentRenderer.  It allows us to break our
429     * porting work into two steps; in the first we bring ParserOptions to
430     * to each callsite to ensure it is made available to the
431     * postprocessing pipeline.  In the second we move this functionality
432     * into the Content hierarchy and out of ParserOutput, which should become
433     * a pure value object.
434     *
435     * @param ParserOptions $popts
436     * @param array $options (since 1.31) Transformations to apply to the HTML
437     *      - allowClone: (bool) Whether to clone the ParserOutput before
438     *     applying transformations. Default is true.
439     *  - allowTOC: (bool) Show the TOC, assuming there were enough headings
440     *     to generate one and `__NOTOC__` wasn't used. Default is true,
441     *     but might be statefully overridden.
442     *  - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
443     *     otherwise the marker will be left in the article (and the skin
444     *     will be responsible for replacing or removing it).  Default is
445     *     true.
446     *  - enableSectionEditLinks: (bool) Include section edit links, assuming
447     *     section edit link tokens are present in the HTML. Default is true,
448     *     but might be statefully overridden.
449     *  - userLang: (Language) Language object used for localizing UX messages,
450     *    for example the heading of the table of contents. If omitted, will
451     *    use the language of the main request context.
452     *  - skin: (Skin) Skin object used for transforming section edit links.
453     *  - unwrap: (bool) Return text without a wrapper div. Default is false,
454     *    meaning a wrapper div will be added if getWrapperDivClass() returns
455     *    a non-empty string.
456     *  - wrapperDivClass: (string) Wrap the output in a div and apply the given
457     *    CSS class to that div. This overrides the output of getWrapperDivClass().
458     *    Setting this to an empty string has the same effect as 'unwrap' => true.
459     *  - deduplicateStyles: (bool) When true, which is the default, `<style>`
460     *    tags with the `data-mw-deduplicate` attribute set are deduplicated by
461     *    value of the attribute: all but the first will be replaced by `<link
462     *    rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
463     *    the scheme-specific-part of the href is the (percent-encoded) value
464     *    of the `data-mw-deduplicate` attribute.
465     *  - absoluteURLs: (bool) use absolute URLs in all links. Default: false
466     *  - includeDebugInfo: (bool) render PP limit report in HTML. Default: false
467     *  It is planned to eventually deprecate this $options array and to be able to
468     *  pass its content in the $popts ParserOptions.
469     * @return ParserOutput
470     */
471    public function runOutputPipeline( ParserOptions $popts, array $options = [] ): ParserOutput {
472        $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
473        $options += [
474            'allowClone' => true,
475            'allowTOC' => true,
476            'injectTOC' => true,
477            'enableSectionEditLinks' => !$this->getOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS ),
478            'userLang' => null,
479            'skin' => null,
480            'unwrap' => false,
481            'wrapperDivClass' => $this->getWrapperDivClass(),
482            'deduplicateStyles' => true,
483            'absoluteURLs' => false,
484            'includeDebugInfo' => false,
485        ];
486        return $pipeline->run( $this, $popts, $options );
487    }
488
489    /**
490     * Adds a comment notice about cache state to the text of the page
491     * @param string $msg
492     * @internal used by ParserCache
493     */
494    public function addCacheMessage( string $msg ): void {
495        $this->mCacheMessage .= $msg;
496    }
497
498    /**
499     * Add a CSS class to use for the wrapping div. If no class is given, no wrapper is added.
500     *
501     * @param string $class
502     */
503    public function addWrapperDivClass( $class ): void {
504        $this->mWrapperDivClasses[$class] = true;
505    }
506
507    /**
508     * Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div
509     * until addWrapperDivClass() is called.
510     */
511    public function clearWrapperDivClass(): void {
512        $this->mWrapperDivClasses = [];
513    }
514
515    /**
516     * Returns the class (or classes) to be used with the wrapper div for this output.
517     * If there is no wrapper class given, no wrapper div should be added.
518     * The wrapper div is added automatically by getText().
519     */
520    public function getWrapperDivClass(): string {
521        return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
522    }
523
524    /**
525     * @param int $id
526     * @since 1.28
527     */
528    public function setSpeculativeRevIdUsed( $id ): void {
529        $this->mSpeculativeRevId = $id;
530    }
531
532    /**
533     * @return int|null
534     * @since 1.28
535     */
536    public function getSpeculativeRevIdUsed(): ?int {
537        return $this->mSpeculativeRevId;
538    }
539
540    /**
541     * @param int $id
542     * @since 1.34
543     */
544    public function setSpeculativePageIdUsed( $id ): void {
545        $this->speculativePageIdUsed = $id;
546    }
547
548    /**
549     * @return int|null
550     * @since 1.34
551     */
552    public function getSpeculativePageIdUsed() {
553        return $this->speculativePageIdUsed;
554    }
555
556    /**
557     * @param string $timestamp TS::MW timestamp
558     * @since 1.34
559     */
560    public function setRevisionTimestampUsed( $timestamp ): void {
561        $this->revisionTimestampUsed = $timestamp;
562    }
563
564    /**
565     * @return string|null TS::MW timestamp or null if not used
566     * @since 1.34
567     */
568    public function getRevisionTimestampUsed() {
569        return $this->revisionTimestampUsed;
570    }
571
572    /**
573     * @param string $hash Lowercase SHA-1 base 36 hash
574     * @since 1.34
575     */
576    public function setRevisionUsedSha1Base36( $hash ): void {
577        if ( $hash === null ) {
578            return; // e.g. RevisionRecord::getSha1() returned null
579        }
580
581        if (
582            $this->revisionUsedSha1Base36 !== null &&
583            $this->revisionUsedSha1Base36 !== $hash
584        ) {
585            $this->revisionUsedSha1Base36 = ''; // mismatched
586        } else {
587            $this->revisionUsedSha1Base36 = $hash;
588        }
589    }
590
591    /**
592     * @return string|null Lowercase SHA-1 base 36 hash, null if unused, or "" on inconsistency
593     * @since 1.34
594     */
595    public function getRevisionUsedSha1Base36() {
596        return $this->revisionUsedSha1Base36;
597    }
598
599    /**
600     * @return string[]
601     * @note Before 1.43, this function returned an array reference.
602     * @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::LANGUAGE)
603     */
604    public function getLanguageLinks() {
605        wfDeprecated( __METHOD__, '1.43' );
606        return $this->getLanguageLinksInternal();
607    }
608
609    /**
610     * @return list<string>
611     */
612    private function getLanguageLinksInternal(): array {
613        $result = [];
614        foreach ( $this->mLanguageLinkMap as $lang => $title ) {
615            $result[] = "$lang:$title";
616        }
617        return $result;
618    }
619
620    /** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::INTERWIKI) */
621    public function getInterwikiLinks() {
622        wfDeprecated( __METHOD__, '1.43' );
623        return $this->mInterwikiLinks;
624    }
625
626    /**
627     * Return the names of the categories on this page.
628     * Unlike ::getCategories(), sort keys are *not* included in the
629     * return value.
630     * @return array<string> The names of the categories
631     * @since 1.38
632     */
633    public function getCategoryNames(): array {
634        # Note that numeric category names get converted to 'int' when
635        # stored as array keys; stringify the keys to ensure they
636        # return to original string form so as not to confuse callers.
637        return array_map( 'strval', array_keys( $this->mCategories ) );
638    }
639
640    /**
641     * Return category names and sort keys as a map.
642     *
643     * BEWARE that numeric category names get converted to 'int' when stored
644     * as array keys.  Because of this, use of this method is not recommended
645     * in new code; using ::getCategoryNames() and ::getCategorySortKey() will
646     * be less error-prone.
647     *
648     * @return array<string|int,string>
649     * @internal
650     */
651    public function getCategoryMap(): array {
652        return $this->mCategories;
653    }
654
655    /**
656     * Return the sort key for a given category name, or `null` if the
657     * category is not present in this ParserOutput.  Returns the
658     * empty string if the category is to use the default sort key.
659     *
660     * @note The effective sort key in the database may vary from what
661     * is returned here; see note in ParserOutput::addCategory().
662     *
663     * @param string $name The category name
664     * @return ?string The sort key for the category, or `null` if the
665     *  category is not present in this ParserOutput
666     * @since 1.40
667     */
668    public function getCategorySortKey( string $name ): ?string {
669        // This API avoids exposing the fact that numeric string category
670        // names are going to be converted to 'int' when used as array
671        // keys for the `mCategories` field.
672        return $this->mCategories[$name] ?? null;
673    }
674
675    /**
676     * @return array<string,string> Maps identifiers to HTML contents
677     * @since 1.25
678     */
679    public function getIndicators(): array {
680        return $this->mIndicators;
681    }
682
683    public function getTitleText(): string {
684        return $this->mTitleText;
685    }
686
687    /**
688     * @return ?TOCData the table of contents data, or null if it hasn't been
689     * set.
690     */
691    public function getTOCData(): ?TOCData {
692        return $this->mTOCData;
693    }
694
695    /**
696     * @internal
697     * @return string
698     */
699    public function getCacheMessage(): string {
700        return $this->mCacheMessage;
701    }
702
703    /**
704     * @internal
705     * @return array
706     */
707    public function getSections(): array {
708        if ( $this->mTOCData !== null ) {
709            return $this->mTOCData->toLegacy();
710        }
711        // For compatibility