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