Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
61.54% covered (warning)
61.54%
112 / 182
CRAP
44.87% covered (danger)
44.87%
582 / 1297
OutputPage
0.00% covered (danger)
0.00%
0 / 1
61.54% covered (warning)
61.54%
112 / 182
35600.22
44.87% covered (danger)
44.87%
582 / 1297
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 redirect
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getRedirect
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setCopyrightUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setStatusCode
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 addMeta
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getMetaTags
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addLink
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getLinkTags
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setCanonicalUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getCanonicalUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addScript
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addScriptFile
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addInlineScript
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 filterModules
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 11
 warnModuleTargetFilter
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 9
 getModules
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 addModules
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getModuleStyles
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 addModuleStyles
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getTarget
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setTarget
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addContentOverride
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 7
 addContentOverrideCallback
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 addHtmlClasses
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getHeadItemsArray
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addHeadItem
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addHeadItems
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 hasHeadItem
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addBodyClasses
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setArticleBodyOnly
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getArticleBodyOnly
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setProperty
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getProperty
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 checkLastModified
100.00% covered (success)
100.00%
1 / 1
10
100.00% covered (success)
100.00%
50 / 50
 getCdnCacheEpoch
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setLastModified
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 setRobotPolicy
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 getRobotPolicy
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setIndexPolicy
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getIndexPolicy
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setFollowPolicy
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getFollowPolicy
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setHTMLTitle
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getHTMLTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setRedirectedFrom
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setPageTitle
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
8 / 8
 getPageTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setDisplayTitle
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getDisplayTitle
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 getUnprefixedDisplayTitle
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 setTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setSubtitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 addSubtitle
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 buildBacklinkSubtitle
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 addBacklinkSubtitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 clearSubtitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getSubtitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setPrintable
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 isPrintable
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 disable
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 isDisabled
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 showNewSectionLink
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 forceHideNewSectionLink
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setSyndicated
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getAdvertisedFeedTypes
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 setFeedAppendQuery
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
7 / 7
 addFeedLink
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 isSyndicated
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getSyndicationLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getFeedAppendQuery
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setArticleFlag
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 isArticle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setArticleRelated
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 isArticleRelated
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setCopyright
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 showsCopyright
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 addLanguageLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setLanguageLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getLanguageLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addCategoryLinks
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
24 / 24
 addCategoryLinksToLBAndGetResult
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 16
 setCategoryLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getCategoryLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getCategories
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
8 / 8
 setIndicators
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getIndicators
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addHelpLink
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
14 / 14
 disallowUserJs
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 10
 getAllowedModules
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 reduceAllowedModules
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 prependHTML
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addHTML
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addElement
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 clearHTML
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getHTML
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 parserOptions
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 9
 setRevisionId
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
2 / 2
 getRevisionId
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isRevisionCurrent
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 setRevisionTimestamp
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getRevisionTimestamp
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setFileVersion
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 getFileVersion
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTemplateIds
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getFileSearchOptions
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addWikiTextAsInterface
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 wrapWikiTextAsInterface
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 addWikiTextAsContent
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 addWikiTextTitleInternal
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 6
 addParserOutputMetadata
0.00% covered (danger)
0.00%
0 / 1
19.95
81.82% covered (warning)
81.82%
45 / 55
 addParserOutputContent
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 addParserOutputText
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 addParserOutput
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 addTemplate
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 parseAsContent
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 parseAsInterface
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 parseInlineAsInterface
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 parseInternal
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 9
 setCdnMaxage
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 lowerCdnMaxage
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 adaptCdnTTL
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
9 / 9
 enableClientCache
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 couldBePublicCached
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 considerCacheSettingsFinal
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getCacheVaryCookies
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
8 / 8
 haveCacheVaryCookies
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
7 / 7
 addVaryHeader
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
7 / 7
 getVaryHeader
0.00% covered (danger)
0.00%
0 / 1
3.07
80.00% covered (warning)
80.00%
4 / 5
 addLinkHeader
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getLinkHeader
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 addAcceptLanguage
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
7 / 7
 preventClickjacking
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 allowClickjacking
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getPreventClickjacking
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getFrameOptions
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
6 / 6
 getOriginTrials
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getReportTo
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 11
 getFeaturePolicyReportOnly
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 sendCacheControl
100.00% covered (success)
100.00%
1 / 1
11
100.00% covered (success)
100.00%
31 / 31
 loadSkinModules
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 6
 output
0.00% covered (danger)
0.00%
0 / 1
506
0.00% covered (danger)
0.00%
0 / 68
 prepareErrorPage
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 10
 showErrorPage
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 11
 showPermissionsErrorPage
0.00% covered (danger)
0.00%
0 / 1
342
0.00% covered (danger)
0.00%
0 / 46
 versionRequired
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 formatPermissionsErrorMessage
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 18
 showLagWarning
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 9
 showFatalError
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 addReturnTo
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 6
 returnToMain
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 13
 getRlClientContext
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 29
 getRlClient
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 40
 headElement
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 46
 getResourceLoader
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 makeResourceLoaderLink
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 combineWrappedStrings
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getBottomScripts
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 9
 getJsConfigVars
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 addJsConfigVars
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 getJSVars
0.00% covered (danger)
0.00%
0 / 1
272
0.00% covered (danger)
0.00%
0 / 79
 getLastSeenUserTalkRevId
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 16
 userCanPreview
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 15
 userCanEditOrCreate
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 4
 getHeadLinksArray
0.00% covered (danger)
0.00%
0 / 1
108.83
59.85% covered (warning)
59.85%
79 / 132
 feedLink
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 addStyle
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 9
 addInlineStyle
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 4
 buildExemptModules
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
18 / 18
 buildCssLinksArray
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 styleLink
0.00% covered (danger)
0.00%
0 / 1
90
0.00% covered (danger)
0.00%
0 / 20
 transformResourcePath
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
14 / 14
 transformFilePath
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 transformCssMedia
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
11 / 11
 addWikiMsg
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 addWikiMsgArray
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 wrapWikiMsg
0.00% covered (danger)
0.00%
0 / 1
3.07
80.00% covered (warning)
80.00%
8 / 10
 isTOCEnabled
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setupOOUI
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 6
 enableOOUI
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 6
 getCSPNonce
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getCSP
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
<?php
/**
 * Preparation for the final page rendering.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\RelPath;
use Wikimedia\WrappedString;
use Wikimedia\WrappedStringList;
/**
 * This is one of the Core classes and should
 * be read at least once by any new developers. Also documented at
 * https://www.mediawiki.org/wiki/Manual:Architectural_modules/OutputPage
 *
 * This class is used to prepare the final rendering. A skin is then
 * applied to the output parameters (links, javascript, html, categories ...).
 *
 * @todo FIXME: Another class handles sending the whole page to the client.
 *
 * Some comments comes from a pairing session between Zak Greant and Antoine Musso
 * in November 2010.
 *
 * @todo document
 */
class OutputPage extends ContextSource {
    use ProtectedHookAccessorTrait;
    /** @var string[][] Should be private. Used with addMeta() which adds "<meta>" */
    protected $mMetatags = [];
    /** @var array */
    protected $mLinktags = [];
    /** @var string|bool */
    protected $mCanonicalUrl = false;
    /**
     * @var string The contents of <h1>
     */
    private $mPageTitle = '';
    /**
     * @var string The displayed title of the page. Different from page title
     * if overridden by display title magic word or hooks. Can contain safe
     * HTML. Different from page title which may contain messages such as
     * "Editing X" which is displayed in h1. This can be used for other places
     * where the page name is referred on the page.
     */
    private $displayTitle;
    /** @var bool See OutputPage::couldBePublicCached. */
    private $cacheIsFinal = false;
    /**
     * @var string Contains all of the "<body>" content. Should be private we
     *   got set/get accessors and the append() method.
     */
    public $mBodytext = '';
    /** @var string Stores contents of "<title>" tag */
    private $mHTMLtitle = '';
    /**
     * @var bool Is the displayed content related to the source of the
     *   corresponding wiki article.
     */
    private $mIsArticle = false;
    /** @var bool Stores "article flag" toggle. */
    private $mIsArticleRelated = true;
    /** @var bool Is the content subject to copyright */
    private $mHasCopyright = false;
    /**
     * @var bool We have to set isPrintable(). Some pages should
     * never be printed (ex: redirections).
     */
    private $mPrintable = false;
    /**
     * @var array Contains the page subtitle. Special pages usually have some
     *   links here. Don't confuse with site subtitle added by skins.
     */
    private $mSubtitle = [];
    /** @var string */
    public $mRedirect = '';
    /** @var int */
    protected $mStatusCode;
    /**
     * @var string Used for sending cache control.
     *   The whole caching system should probably be moved into its own class.
     */
    protected $mLastModified = '';
    /** @var array */
    protected $mCategoryLinks = [];
    /** @var array */
    protected $mCategories = [
        'hidden' => [],
        'normal' => [],
    ];
    /** @var array */
    protected $mIndicators = [];
    /** @var array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') */
    private $mLanguageLinks = [];
    /**
     * Used for JavaScript (predates ResourceLoader)
     * @todo We should split JS / CSS.
     * mScripts content is inserted as is in "<head>" by Skin. This might
     * contain either a link to a stylesheet or inline CSS.
     */
    private $mScripts = '';
    /** @var string Inline CSS styles. Use addInlineStyle() sparingly */
    protected $mInlineStyles = '';
    /**
     * @var string Used by skin template.
     * Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle );
     */
    public $mPageLinkTitle = '';
    /**
     * Additional <html> classes; This should be rarely modified; prefer mAdditionalBodyClasses.
     * @var array
     */
    protected $mAdditionalHtmlClasses = [];
    /** @var array Array of elements in "<head>". Parser might add its own headers! */
    protected $mHeadItems = [];
    /** @var array Additional <body> classes; there are also <body> classes from other sources */
    protected $mAdditionalBodyClasses = [];
    /** @var array */
    protected $mModules = [];
    /** @var array */
    protected $mModuleStyles = [];
    /** @var ResourceLoader */
    protected $mResourceLoader;
    /** @var ResourceLoaderClientHtml */
    private $rlClient;
    /** @var ResourceLoaderContext */
    private $rlClientContext;
    /** @var array */
    private $rlExemptStyleModules;
    /** @var array */
    protected $mJsConfigVars = [];
    /** @var array */
    protected $mTemplateIds = [];
    /** @var array */
    protected $mImageTimeKeys = [];
    /** @var string */
    public $mRedirectCode = '';
    protected $mFeedLinksAppendQuery = null;
    /** @var array
     * What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
     * @see ResourceLoaderModule::$origin
     * ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden;
     */
    protected $mAllowedModules = [
        ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
    ];
    /** @var bool Whether output is disabled.  If this is true, the 'output' method will do nothing. */
    protected $mDoNothing = false;
    // Parser related.
    /** @var int */
    protected $mContainsNewMagic = 0;
    /**
     * lazy initialised, use parserOptions()
     * @var ParserOptions
     */
    protected $mParserOptions = null;
    /**
     * Handles the Atom / RSS links.
     * We probably only support Atom in 2011.
     * @see $wgAdvertisedFeedTypes
     */
    private $mFeedLinks = [];
    // Gwicke work on squid caching? Roughly from 2003.
    protected $mEnableClientCache = true;
    /** @var bool Flag if output should only contain the body of the article. */
    private $mArticleBodyOnly = false;
    /** @var bool */
    protected $mNewSectionLink = false;
    /** @var bool */
    protected $mHideNewSectionLink = false;
    /**
     * @var bool Comes from the parser. This was probably made to load CSS/JS
     * only if we had "<gallery>". Used directly in CategoryPage.php.
     * Looks like ResourceLoader can replace this.
     */
    public $mNoGallery = false;
    /** @var int Cache stuff. Looks like mEnableClientCache */
    protected $mCdnMaxage = 0;
    /** @var int Upper limit on mCdnMaxage */
    protected $mCdnMaxageLimit = INF;
    /**
     * @var bool Controls if anti-clickjacking / frame-breaking headers will
     * be sent. This should be done for pages where edit actions are possible.
     * Setters: $this->preventClickjacking() and $this->allowClickjacking().
     */
    protected $mPreventClickjacking = true;
    /** @var int To include the variable {{REVISIONID}} */
    private $mRevisionId = null;
    /** @var string */
    private $mRevisionTimestamp = null;
    /** @var array */
    protected $mFileVersion = null;
    /**
     * @var array An array of stylesheet filenames (relative from skins path),
     * with options for CSS media, IE conditions, and RTL/LTR direction.
     * For internal use; add settings in the skin via $this->addStyle()
     *
     * Style again! This seems like a code duplication since we already have
     * mStyles. This is what makes Open Source amazing.
     */
    protected $styles = [];
    private $mIndexPolicy = 'index';
    private $mFollowPolicy = 'follow';
    /**
     * @var array Headers that cause the cache to vary.  Key is header name,
     * value should always be null.  (Value was an array of options for
     * the `Key` header, which was deprecated in 1.32 and removed in 1.34.)
     */
    private $mVaryHeader = [
        'Accept-Encoding' => null,
    ];
    /**
     * If the current page was reached through a redirect, $mRedirectedFrom contains the Title
     * of the redirect.
     *
     * @var Title
     */
    private $mRedirectedFrom = null;
    /**
     * Additional key => value data
     */
    private $mProperties = [];
    /**
     * @var string|null ResourceLoader target for load.php links. If null, will be omitted
     */
    private $mTarget = null;
    /**
     * @var bool Whether parser output contains a table of contents
     */
    private $mEnableTOC = false;
    /**
     * @var string|null The URL to send in a <link> element with rel=license
     */
    private $copyrightUrl;
    /** @var array Profiling data */
    private $limitReportJSData = [];
    /** @var array Map Title to Content */
    private $contentOverrides = [];
    /** @var callable[] */
    private $contentOverrideCallbacks = [];
    /**
     * Link: header contents
     */
    private $mLinkHeader = [];
    /**
     * @var ContentSecurityPolicy
     */
    private $CSP;
    /**
     * @var array A cache of the names of the cookies that will influence the cache
     */
    private static $cacheVaryCookies = null;
    /**
     * Constructor for OutputPage. This should not be called directly.
     * Instead a new RequestContext should be created and it will implicitly create
     * a OutputPage tied to that context.
     * @param IContextSource $context
     */
    public function __construct( IContextSource $context ) {
        $this->setContext( $context );
        $this->CSP = new ContentSecurityPolicy(
            $context->getRequest()->response(),
            $context->getConfig(),
            $this->getHookContainer()
        );
    }
    /**
     * Redirect to $url rather than displaying the normal page
     *
     * @param string $url
     * @param string|int $responsecode HTTP status code
     */
    public function redirect( $url, $responsecode = '302' ) {
        # Strip newlines as a paranoia check for header injection in PHP<5.1.2
        $this->mRedirect = str_replace( "\n", '', $url );
        $this->mRedirectCode = (string)$responsecode;
    }
    /**
     * Get the URL to redirect to, or an empty string if not redirect URL set
     *
     * @return string
     */
    public function getRedirect() {
        return $this->mRedirect;
    }
    /**
     * Set the copyright URL to send with the output.
     * Empty string to omit, null to reset.
     *
     * @since 1.26
     *
     * @param string|null $url
     */
    public function setCopyrightUrl( $url ) {
        $this->copyrightUrl = $url;
    }
    /**
     * Set the HTTP status code to send with the output.
     *
     * @param int $statusCode
     */
    public function setStatusCode( $statusCode ) {
        $this->mStatusCode = $statusCode;
    }
    /**
     * Add a new "<meta>" tag
     * To add an http-equiv meta tag, precede the name with "http:"
     *
     * @param string $name Name of the meta tag
     * @param string $val Value of the meta tag
     */
    public function addMeta( $name, $val ) {
        $this->mMetatags[] = [ $name, $val ];
    }
    /**
     * Returns the current <meta> tags
     *
     * @since 1.25
     * @return array
     */
    public function getMetaTags() {
        return $this->mMetatags;
    }
    /**
     * Add a new \<link\> tag to the page header.
     *
     * Note: use setCanonicalUrl() for rel=canonical.
     *
     * @param array $linkarr Associative array of attributes.
     */
    public function addLink( array $linkarr ) {
        $this->mLinktags[] = $linkarr;
    }
    /**
     * Returns the current <link> tags
     *
     * @since 1.25
     * @return array
     */
    public function getLinkTags() {
        return $this->mLinktags;
    }
    /**
     * Set the URL to be used for the <link rel=canonical>. This should be used
     * in preference to addLink(), to avoid duplicate link tags.
     * @param string $url
     */
    public function setCanonicalUrl( $url ) {
        $this->mCanonicalUrl = $url;
    }
    /**
     * Returns the URL to be used for the <link rel=canonical> if
     * one is set.
     *
     * @since 1.25
     * @return bool|string
     */
    public function getCanonicalUrl() {
        return $this->mCanonicalUrl;
    }
    /**
     * Add raw HTML to the list of scripts (including \<script\> tag, etc.)
     * Internal use only. Use OutputPage::addModules() or OutputPage::addJsConfigVars()
     * if possible.
     *
     * @param string $script Raw HTML
     */
    public function addScript( $script ) {
        $this->mScripts .= $script;
    }
    /**
     * Add a JavaScript file to be loaded as `<script>` on this page.
     *
     * Internal use only. Use OutputPage::addModules() if possible.
     *
     * @param string $file URL to file (absolute path, protocol-relative, or full url)
     * @param string|null $unused Previously used to change the cache-busting query parameter
     */
    public function addScriptFile( $file, $unused = null ) {
        $this->addScript( Html::linkedScript( $file, $this->CSP->getNonce() ) );
    }
    /**
     * Add a self-contained script tag with the given contents
     * Internal use only. Use OutputPage::addModules() if possible.
     *
     * @param string $script JavaScript text, no script tags
     */
    public function addInlineScript( $script ) {
        $this->mScripts .= Html::inlineScript( "\n$script\n", $this->CSP->getNonce() ) . "\n";
    }
    /**
     * Filter an array of modules to remove insufficiently trustworthy members, and modules
     * which are no longer registered (eg a page is cached before an extension is disabled)
     * @param array $modules
     * @param string|null $position Unused
     * @param string $type
     * @return array
     */
    protected function filterModules( array $modules, $position = null,
        $type = ResourceLoaderModule::TYPE_COMBINED
    ) {
        $resourceLoader = $this->getResourceLoader();
        $filteredModules = [];
        foreach ( $modules as $val ) {
            $module = $resourceLoader->getModule( $val );
            if ( $module instanceof ResourceLoaderModule
                && $module->getOrigin() <= $this->getAllowedModules( $type )
            ) {
                if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
                    $this->warnModuleTargetFilter( $module->getName() );
                    continue;
                }
                $filteredModules[] = $val;
            }
        }
        return $filteredModules;
    }
    private function warnModuleTargetFilter( $moduleName ) {
        static $warnings = [];
        if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
            return;
        }
        $warnings[$this->mTarget][$moduleName] = true;
        $this->getResourceLoader()->getLogger()->debug(
            'Module "{module}" not loadable on target "{target}".',
            [
                'module' => $moduleName,
                'target' => $this->mTarget,
            ]
        );
    }
    /**
     * Get the list of modules to include on this page
     *
     * @param bool $filter Whether to filter out insufficiently trustworthy modules
     * @param string|null $position Unused
     * @param string $param
     * @param string $type
     * @return array Array of module names
     */
    public function getModules( $filter = false, $position = null, $param = 'mModules',
        $type = ResourceLoaderModule::TYPE_COMBINED
    ) {
        $modules = array_values( array_unique( $this->$param ) );
        return $filter
            ? $this->filterModules( $modules, null, $type )
            : $modules;
    }
    /**
     * Load one or more ResourceLoader modules on this page.
     *
     * @param string|array $modules Module name (string) or array of module names
     */
    public function addModules( $modules ) {
        $this->mModules = array_merge( $this->mModules, (array)$modules );
    }
    /**
     * Get the list of style-only modules to load on this page.
     *
     * @param bool $filter
     * @param string|null $position Unused
     * @return array Array of module names
     */
    public function getModuleStyles( $filter = false, $position = null ) {
        return $this->getModules( $filter, null, 'mModuleStyles',
            ResourceLoaderModule::TYPE_STYLES
        );
    }
    /**
     * Load the styles of one or more style-only ResourceLoader modules on this page.
     *
     * Module styles added through this function will be loaded as a stylesheet,
     * using a standard `<link rel=stylesheet>` HTML tag, rather than as a combined
     * Javascript and CSS package. Thus, they will even load when JavaScript is disabled.
     *
     * @param string|array $modules Module name (string) or array of module names
     */
    public function addModuleStyles( $modules ) {
        $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
    }
    /**
     * @return null|string ResourceLoader target
     */
    public function getTarget() {
        return $this->mTarget;
    }
    /**
     * Sets ResourceLoader target for load.php links. If null, will be omitted
     *
     * @param string|null $target
     */
    public function setTarget( $target ) {
        $this->mTarget = $target;
    }
    /**
     * Add a mapping from a LinkTarget to a Content, for things like page preview.
     * @see self::addContentOverrideCallback()
     * @since 1.32
     * @param LinkTarget $target
     * @param Content $content
     */
    public function addContentOverride( LinkTarget $target, Content $content ) {
        if ( !$this->contentOverrides ) {
            // Register a callback for $this->contentOverrides on the first call
            $this->addContentOverrideCallback( function ( LinkTarget $target ) {
                $key = $target->getNamespace() . ':' . $target->getDBkey();
                return $this->contentOverrides[$key] ?? null;
            } );
        }
        $key = $target->getNamespace() . ':' . $target->getDBkey();
        $this->contentOverrides[$key] = $content;
    }
    /**
     * Add a callback for mapping from a Title to a Content object, for things
     * like page preview.
     * @see ResourceLoaderContext::getContentOverrideCallback()
     * @since 1.32
     * @param callable $callback
     */
    public function addContentOverrideCallback( callable $callback ) {
        $this->contentOverrideCallbacks[] = $callback;
    }
    /**
     * Add a class to the <html> element. This should rarely be used.
     * Instead use OutputPage::addBodyClasses() if possible.
     *
     * @unstable Experimental since 1.35. Prefer OutputPage::addBodyClasses()
     * @param string|string[] $classes One or more classes to add
     */
    public function addHtmlClasses( $classes ) {
        $this->mAdditionalHtmlClasses = array_merge( $this->mAdditionalHtmlClasses, (array)$classes );
    }
    /**
     * Get an array of head items
     *
     * @return array
     */
    public function getHeadItemsArray() {
        return $this->mHeadItems;
    }
    /**
     * Add or replace a head item to the output
     *
     * Whenever possible, use more specific options like ResourceLoader modules,
     * OutputPage::addLink(), OutputPage::addMeta() and OutputPage::addFeedLink()
     * Fallback options for those are: OutputPage::addStyle, OutputPage::addScript(),
     * OutputPage::addInlineScript() and OutputPage::addInlineStyle()
     * This would be your very LAST fallback.
     *
     * @param string $name Item name
     * @param string $value Raw HTML
     */
    public function addHeadItem( $name, $value ) {
        $this->mHeadItems[$name] = $value;
    }
    /**
     * Add one or more head items to the output
     *
     * @since 1.28
     * @param string|string[] $values Raw HTML
     */
    public function addHeadItems( $values ) {
        $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
    }
    /**
     * Check if the header item $name is already set
     *
     * @param string $name Item name
     * @return bool
     */
    public function hasHeadItem( $name ) {
        return isset( $this->mHeadItems[$name] );
    }
    /**
     * Add a class to the <body> element
     *
     * @since 1.30
     * @param string|string[] $classes One or more classes to add
     */
    public function addBodyClasses( $classes ) {
        $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
    }
    /**
     * Set whether the output should only contain the body of the article,
     * without any skin, sidebar, etc.
     * Used e.g. when calling with "action=render".
     *
     * @param bool $only Whether to output only the body of the article
     */
    public function setArticleBodyOnly( $only ) {
        $this->mArticleBodyOnly = $only;
    }
    /**
     * Return whether the output will contain only the body of the article
     *
     * @return bool
     */
    public function getArticleBodyOnly() {
        return $this->mArticleBodyOnly;
    }
    /**
     * Set an additional output property
     * @since 1.21
     *
     * @param string $name
     * @param mixed $value
     */
    public function setProperty( $name, $value ) {
        $this->mProperties[$name] = $value;
    }
    /**
     * Get an additional output property
     * @since 1.21
     *
     * @param string $name
     * @return mixed Property value or null if not found
     */
    public function getProperty( $name ) {
        return $this->mProperties[$name] ?? null;
    }
    /**
     * checkLastModified tells the client to use the client-cached page if
     * possible. If successful, the OutputPage is disabled so that
     * any future call to OutputPage->output() have no effect.
     *
     * Side effect: sets mLastModified for Last-Modified header
     *
     * @param string $timestamp
     *
     * @return bool True if cache-ok headers was sent.
     */
    public function checkLastModified( $timestamp ) {
        if ( !$timestamp || $timestamp == '19700101000000' ) {
            wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP" );
            return false;
        }
        $config = $this->getConfig();
        if ( !$config->get( 'CachePages' ) ) {
            wfDebug( __METHOD__ . ": CACHE DISABLED" );
            return false;
        }
        $timestamp = wfTimestamp( TS_MW, $timestamp );
        $modifiedTimes = [
            'page' => $timestamp,
            'user' => $this->getUser()->getTouched(),
            'epoch' => $config->get( 'CacheEpoch' )
        ];
        if ( $config->get( 'UseCdn' ) ) {
            $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
                time(),
                $config->get( 'CdnMaxAge' )
            ) );
        }
        $this->getHookRunner()->onOutputPageCheckLastModified( $modifiedTimes, $this );
        $maxModified = max( $modifiedTimes );
        $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
        $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
        if ( $clientHeader === false ) {
            wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
            return false;
        }
        # IE sends sizes after the date like this:
        # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
        # this breaks strtotime().
        $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
        Wikimedia\suppressWarnings(); // E_STRICT system time warnings
        $clientHeaderTime = strtotime( $clientHeader );
        Wikimedia\restoreWarnings();
        if ( !$clientHeaderTime ) {
            wfDebug( __METHOD__
                . ": unable to parse the client's If-Modified-Since header: $clientHeader" );
            return false;
        }
        $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
        # Make debug info
        $info = '';
        foreach ( $modifiedTimes as $name => $value ) {
            if ( $info !== '' ) {
                $info .= ', ';
            }
            $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
        }
        wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
            wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
        wfDebug( __METHOD__ . ": effective Last-Modified: " .
            wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
        if ( $clientHeaderTime < $maxModified ) {
            wfDebug( __METHOD__ . ": STALE, $info", 'private' );
            return false;
        }
        # Not modified
        # Give a 304 Not Modified response code and disable body output
        wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
        ini_set( 'zlib.output_compression', 0 );
        $this->getRequest()->response()->statusHeader( 304 );
        $this->sendCacheControl();
        $this->disable();
        // Don't output a compressed blob when using ob_gzhandler;
        // it's technically against HTTP spec and seems to confuse
        // Firefox when the response gets split over two packets.
        wfClearOutputBuffers();
        return true;
    }
    /**
     * @param int $reqTime Time of request (eg. now)
     * @param int $maxAge Cache TTL in seconds
     * @return int Timestamp
     */
    private function getCdnCacheEpoch( $reqTime, $maxAge ) {
        // Ensure Last-Modified is never more than $wgCdnMaxAge in the past,
        // because even if the wiki page content hasn't changed since, static
        // resources may have changed (skin HTML, interface messages, urls, etc.)
        // and must roll-over in a timely manner (T46570)
        return $reqTime - $maxAge;
    }
    /**
     * Override the last modified timestamp
     *
     * @param string $timestamp New timestamp, in a format readable by
     *        wfTimestamp()
     */
    public function setLastModified( $timestamp ) {
        $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
    }
    /**
     * Set the robot policy for the page: <http://www.robotstxt.org/meta.html>
     *
     * @param string $policy The literal string to output as the contents of
     *   the meta tag.  Will be parsed according to the spec and output in
     *   standardized form.
     * @return null
     */
    public function setRobotPolicy( $policy ) {
        $policy = Article::formatRobotPolicy( $policy );
        if ( isset( $policy['index'] ) ) {
            $this->setIndexPolicy( $policy['index'] );
        }
        if ( isset( $policy['follow'] ) ) {
            $this->setFollowPolicy( $policy['follow'] );
        }
    }
    /**
     * Get the current robot policy for the page as a string in the form
     * <index policy>,<follow policy>.
     *
     * @return string
     */
    public function getRobotPolicy() {
        return "{$this->mIndexPolicy},{$this->mFollowPolicy}";
    }
    /**
     * Set the index policy for the page, but leave the follow policy un-
     * touched.
     *
     * @param string $policy Either 'index' or 'noindex'.
     * @return null
     */
    public function setIndexPolicy( $policy ) {
        $policy = trim( $policy );
        if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
            $this->mIndexPolicy = $policy;
        }
    }
    /**
     * Get the current index policy for the page as a string.
     *
     * @return string
     */
    public function getIndexPolicy() {
        return $this->mIndexPolicy;
    }
    /**
     * Set the follow policy for the page, but leave the index policy un-
     * touched.
     *
     * @param string $policy Either 'follow' or 'nofollow'.
     * @return null
     */
    public function setFollowPolicy( $policy ) {
        $policy = trim( $policy );
        if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
            $this->mFollowPolicy = $policy;
        }
    }
    /**
     * Get the current follow policy for the page as a string.
     *
     * @return string
     */
    public function getFollowPolicy() {
        return $this->mFollowPolicy;
    }
    /**
     * "HTML title" means the contents of "<title>".
     * It is stored as plain, unescaped text and will be run through htmlspecialchars in the skin file.
     *
     * @param string|Message $name
     */
    public function setHTMLTitle( $name ) {
        if ( $name instanceof Message ) {
            $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
        } else {
            $this->mHTMLtitle = $name;
        }
    }
    /**
     * Return the "HTML title", i.e. the content of the "<title>" tag.
     *
     * @return string
     */
    public function getHTMLTitle() {
        return $this->mHTMLtitle;
    }
    /**
     * Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
     *
     * @param Title $t
     */
    public function setRedirectedFrom( $t ) {
        $this->mRedirectedFrom = $t;
    }
    /**
     * "Page title" means the contents of \<h1\>. It is stored as a valid HTML
     * fragment. This function allows good tags like \<sup\> in the \<h1\> tag,
     * but not bad tags like \<script\>. This function automatically sets
     * \<title\> to the same content as \<h1\> but with all tags removed. Bad
     * tags that were escaped in \<h1\> will still be escaped in \<title\>, and
     * good tags like \<i\> will be dropped entirely.
     *
     * @param string|Message $name
     * @param-taint $name tainted
     * Phan-taint-check gets very confused by $name being either a string or a Message
     */
    public function setPageTitle( $name ) {
        if ( $name instanceof Message ) {
            $name = $name->setContext( $this->getContext() )->text();
        }
        # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
        # but leave "<i>foobar</i>" alone
        $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
        $this->mPageTitle = $nameWithTags;
        # change "<i>foo&amp;bar</i>" to "foo&bar"
        $this->setHTMLTitle(
            $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
                ->inContentLanguage()
        );
    }
    /**
     * Return the "page title", i.e. the content of the \<h1\> tag.
     *
     * @return string
     */
    public function getPageTitle() {
        return $this->mPageTitle;
    }
    /**
     * Same as page title but only contains name of the page, not any other text.
     *
     * @since 1.32
     * @param string $html Page title text.
     * @see OutputPage::setPageTitle
     */
    public function setDisplayTitle( $html ) {
        $this->displayTitle = $html;
    }
    /**
     * Returns page display title.
     *
     * Performs some normalization, but this not as strict the magic word.
     *
     * @since 1.32
     * @return string HTML
     */
    public function getDisplayTitle() {
        $html = $this->displayTitle;
        if ( $html === null ) {
            $html = $this->getTitle()->getPrefixedText();
        }
        return Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $html ) );
    }
    /**
     * Returns page display title without namespace prefix if possible.
     *
     * @since 1.32
     * @return string HTML
     */
    public function getUnprefixedDisplayTitle() {
        $text = $this->getDisplayTitle();
        $nsPrefix = $this->getTitle()->getNsText() . ':';
        $prefix = preg_quote( $nsPrefix, '/' );
        return preg_replace( "/^$prefix/i", '', $text );
    }
    /**
     * Set the Title object to use
     *
     * @param Title $t
     */
    public function setTitle( Title $t ) {
        // @phan-suppress-next-next-line PhanUndeclaredMethod
        // @fixme Not all implementations of IContextSource have this method!
        $this->getContext()->setTitle( $t );
    }
    /**
     * Replace the subtitle with $str
     *
     * @param string|Message $str New value of the subtitle. String should be safe HTML.
     */
    public function setSubtitle( $str ) {
        $this->clearSubtitle();
        $this->addSubtitle( $str );
    }
    /**
     * Add $str to the subtitle
     *
     * @param string|Message $str String or Message to add to the subtitle. String should be safe HTML.
     */
    public function addSubtitle( $str ) {
        if ( $str instanceof Message ) {
            $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
        } else {
            $this->mSubtitle[] = $str;
        }
    }
    /**
     * Build message object for a subtitle containing a backlink to a page
     *
     * @param Title $title Title to link to
     * @param array $query Array of additional parameters to include in the link
     * @return Message
     * @since 1.25
     */
    public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
        if ( $title->isRedirect() ) {
            $query['redirect'] = 'no';
        }
        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
        return wfMessage( 'backlinksubtitle' )
            ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
    }
    /**
     * Add a subtitle containing a backlink to a page
     *
     * @param Title $title Title to link to
     * @param array $query Array of additional parameters to include in the link
     */
    public function addBacklinkSubtitle( Title $title, $query = [] ) {
        $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
    }
    /**
     * Clear the subtitles
     */
    public function clearSubtitle() {
        $this->mSubtitle = [];
    }
    /**
     * Get the subtitle
     *
     * @return string
     */
    public function getSubtitle() {
        return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
    }
    /**
     * Set the page as printable, i.e. it'll be displayed with all
     * print styles included
     */
    public function setPrintable() {
        $this->mPrintable = true;
    }
    /**
     * Return whether the page is "printable"
     *
     * @return bool
     */
    public function isPrintable() {
        return $this->mPrintable;
    }
    /**
     * Disable output completely, i.e. calling output() will have no effect
     */
    public function disable() {
        $this->mDoNothing = true;
    }
    /**
     * Return whether the output will be completely disabled
     *
     * @return bool
     */
    public function isDisabled() {
        return $this->mDoNothing;
    }
    /**
     * Show an "add new section" link?
     *
     * @return bool
     */
    public function showNewSectionLink() {
        return $this->mNewSectionLink;
    }
    /**
     * Forcibly hide the new section link?
     *
     * @return bool
     */
    public function forceHideNewSectionLink() {
        return $this->mHideNewSectionLink;
    }
    /**
     * Add or remove feed links in the page header
     * This is mainly kept for backward compatibility, see OutputPage::addFeedLink()
     * for the new version
     * @see addFeedLink()
     *
     * @param bool $show True: add default feeds, false: remove all feeds
     */
    public function setSyndicated( $show = true ) {
        if ( $show ) {
            $this->setFeedAppendQuery( false );
        } else {
            $this->mFeedLinks = [];
        }
    }
    /**
     * Return effective list of advertised feed types
     * @see addFeedLink()
     *
     * @return array Array of feed type names ( 'rss', 'atom' )
     */
    protected function getAdvertisedFeedTypes() {
        if ( $this->getConfig()->get( 'Feed' ) ) {
            return $this->getConfig()->get( 'AdvertisedFeedTypes' );
        } else {
            return [];
        }
    }
    /**
     * Add default feeds to the page header
     * This is mainly kept for backward compatibility, see OutputPage::addFeedLink()
     * for the new version
     * @see addFeedLink()
     *
     * @param string $val Query to append to feed links or false to output
     *        default links
     */
    public function setFeedAppendQuery( $val ) {
        $this->mFeedLinks = [];
        foreach ( $this->getAdvertisedFeedTypes() as $type ) {
            $query = "feed=$type";
            if ( is_string( $val ) ) {
                $query .= '&' . $val;
            }
            $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
        }
    }
    /**
     * Add a feed link to the page header
     *
     * @param string $format Feed type, should be a key of $wgFeedClasses
     * @param string $href URL
     */
    public function addFeedLink( $format, $href ) {
        if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
            $this->mFeedLinks[$format] = $href;
        }
    }
    /**
     * Should we output feed links for this page?
     * @return bool
     */
    public function isSyndicated() {
        return count( $this->mFeedLinks ) > 0;
    }
    /**
     * Return URLs for each supported syndication format for this page.
     * @return array Associating format keys with URLs
     */
    public function getSyndicationLinks() {
        return $this->mFeedLinks;
    }
    /**
     * Will currently always return null
     *
     * @return null
     */
    public function getFeedAppendQuery() {
        return $this->mFeedLinksAppendQuery;
    }
    /**
     * Set whether the displayed content is related to the source of the
     * corresponding article on the wiki
     * Setting true will cause the change "article related" toggle to true
     *
     * @param bool $newVal
     */
    public function setArticleFlag( $newVal ) {
        $this->mIsArticle = $newVal;
        if ( $newVal ) {
            $this->mIsArticleRelated = $newVal;
        }
    }
    /**
     * Return whether the content displayed page is related to the source of
     * the corresponding article on the wiki
     *
     * @return bool
     */
    public function isArticle() {
        return $this->mIsArticle;
    }
    /**
     * Set whether this page is related an article on the wiki
     * Setting false will cause the change of "article flag" toggle to false
     *
     * @param bool $newVal
     */
    public function setArticleRelated( $newVal ) {
        $this->mIsArticleRelated = $newVal;
        if ( !$newVal ) {
            $this->mIsArticle = false;
        }
    }
    /**
     * Return whether this page is related an article on the wiki
     *
     * @return bool
     */
    public function isArticleRelated() {
        return $this->mIsArticleRelated;
    }
    /**
     * Set whether the standard copyright should be shown for the current page.
     *
     * @param bool $hasCopyright
     */
    public function setCopyright( $hasCopyright ) {
        $this->mHasCopyright = $hasCopyright;
    }
    /**
     * Return whether the standard copyright should be shown for the current page.
     * By default, it is true for all articles but other pages
     * can signal it by using setCopyright( true ).
     *
     * Used by SkinTemplate to decided whether to show the copyright.
     *
     * @return bool
     */
    public function showsCopyright() {
        return $this->isArticle() || $this->mHasCopyright;
    }
    /**
     * Add new language links
     *
     * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles
     *                               (e.g. 'fr:Test page')
     */
    public function addLanguageLinks( array $newLinkArray ) {
        $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
    }
    /**
     * Reset the language links and add new language links
     *
     * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles
     *                               (e.g. 'fr:Test page')
     */
    public function setLanguageLinks( array $newLinkArray ) {
        $this->mLanguageLinks = $newLinkArray;
    }
    /**
     * Get the list of language links
     *
     * @return string[] Array of interwiki-prefixed (non DB key) titles (e.g. 'fr:Test page')
     */
    public function getLanguageLinks() {
        return $this->mLanguageLinks;
    }
    /**
     * Add an array of categories, with names in the keys
     *
     * @param array $categories Mapping category name => sort key
     */
    public function addCategoryLinks( array $categories ) {
        if ( !$categories ) {
            return;
        }
        $res = $this->addCategoryLinksToLBAndGetResult( $categories );
        # Set all the values to 'normal'.
        $categories = array_fill_keys( array_keys( $categories ), 'normal' );
        # Mark hidden categories
        foreach ( $res as $row ) {
            if ( isset( $row->pp_value ) ) {
                $categories[$row->page_title] = 'hidden';
            }
        }
        # Add the remaining categories to the skin
        if ( $this->getHookRunner()->onOutputPageMakeCategoryLinks(
            $this, $categories, $this->mCategoryLinks )
        ) {
            $services = MediaWikiServices::getInstance();
            $linkRenderer = $services->getLinkRenderer();
            foreach ( $categories as $category => $type ) {
                // array keys will cast numeric category names to ints, so cast back to string
                $category = (string)$category;
                $origcategory = $category;
                $title = Title::makeTitleSafe( NS_CATEGORY, $category );
                if ( !$title ) {
                    continue;
                }
                $services->getContentLanguage()->findVariantLink( $category, $title, true );
                if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
                    continue;
                }
                $text = $services->getContentLanguage()->convertHtml( $title->getText() );
                $this->mCategories[$type][] = $title->getText();
                $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
            }
        }
    }
    /**
     * @param array $categories
     * @return bool|IResultWrapper
     */
    protected function addCategoryLinksToLBAndGetResult( array $categories ) {
        # Add the links to a LinkBatch
        $arr = [ NS_CATEGORY => $categories ];
        $lb = new LinkBatch;
        $lb->setArray( $arr );
        # Fetch existence plus the hiddencat property
        $dbr = wfGetDB( DB_REPLICA );
        $fields = array_merge(
            LinkCache::getSelectFields(),
            [ 'page_namespace', 'page_title', 'pp_value' ]
        );
        $res = $dbr->select( [ 'page', 'page_props' ],
            $fields,
            $lb->constructSet( 'page', $dbr ),
            __METHOD__,
            [],
            [ 'page_props' => [ 'LEFT JOIN', [
                'pp_propname' => 'hiddencat',
                'pp_page = page_id'
            ] ] ]
        );
        # Add the results to the link cache
        $linkCache = MediaWikiServices::getInstance()->getLinkCache();
        $lb->addResultToCache( $linkCache, $res );
        return $res;
    }
    /**
     * Reset the category links (but not the category list) and add $categories
     *
     * @param array $categories Mapping category name => sort key
     */
    public function setCategoryLinks( array $categories ) {
        $this->mCategoryLinks = [];
        $this->addCategoryLinks( $categories );
    }
    /**
     * Get the list of category links, in a 2-D array with the following format:
     * $arr[$type][] = $link, where $type is either "normal" or "hidden" (for
     * hidden categories) and $link a HTML fragment with a link to the category
     * page
     *
     * @return array
     */
    public function getCategoryLinks() {
        return $this->mCategoryLinks;
    }
    /**
     * Get the list of category names this page belongs to.
     *
     * @param string $type The type of categories which should be returned. Possible values:
     *  * all: all categories of all types
     *  * hidden: only the hidden categories
     *  * normal: all categories, except hidden categories
     * @return array Array of strings
     */
    public function getCategories( $type = 'all' ) {
        if ( $type === 'all' ) {
            $allCategories = [];
            foreach ( $this->mCategories as $categories ) {
                $allCategories = array_merge( $allCategories, $categories );
            }
            return $allCategories;
        }
        if ( !isset( $this->mCategories[$type] ) ) {
            throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
        }
        return $this->mCategories[$type];
    }
    /**
     * Add an array of indicators, with their identifiers as array
     * keys and HTML contents as values.
     *
     * In case of duplicate keys, existing values are overwritten.
     *
     * @param array $indicators
     * @since 1.25
     */
    public function setIndicators( array $indicators ) {
        $this->mIndicators = $indicators + $this->mIndicators;
        // Keep ordered by key
        ksort( $this->mIndicators );
    }
    /**
     * Get the indicators associated with this page.
     *
     * The array will be internally ordered by item keys.
     *
     * @return array Keys: identifiers, values: HTML contents
     * @since 1.25
     */
    public function getIndicators() {
        return $this->mIndicators;
    }
    /**
     * Adds help link with an icon via page indicators.
     * Link target can be overridden by a local message containing a wikilink:
     * the message key is: lowercase action or special page name + '-helppage'.
     * @param string $to Target MediaWiki.org page title or encoded URL.
     * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
     * @since 1.25
     */
    public function addHelpLink( $to, $overrideBaseUrl = false ) {
        $this->addModuleStyles( 'mediawiki.helplink' );
        $text = $this->msg( 'helppage-top-gethelp' )->escaped();
        if ( $overrideBaseUrl ) {
            $helpUrl = $to;
        } else {
            $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
            $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
        }
        $link = Html::rawElement(
            'a',
            [
                'href' => $helpUrl,
                'target' => '_blank',
                'class' => 'mw-helplink',
            ],
            $text
        );
        $this->setIndicators( [ 'mw-helplink' => $link ] );
    }
    /**
     * Do not allow scripts which can be modified by wiki users to load on this page;
     * only allow scripts bundled with, or generated by, the software.
     * Site-wide styles are controlled by a config setting, since they can be
     * used to create a custom skin/theme, but not user-specific ones.
     *
     * @todo this should be given a more accurate name
     */
    public function disallowUserJs() {
        $this->reduceAllowedModules(
            ResourceLoaderModule::TYPE_SCRIPTS,
            ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
        );
        // Site-wide styles are controlled by a config setting, see T73621
        // for background on why. User styles are never allowed.
        if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
            $styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE;
        } else {
            $styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL;
        }
        $this->reduceAllowedModules(
            ResourceLoaderModule::TYPE_STYLES,
            $styleOrigin
        );
    }
    /**
     * Show what level of JavaScript / CSS untrustworthiness is allowed on this page
     * @see ResourceLoaderModule::$origin
     * @param string $type ResourceLoaderModule TYPE_ constant
     * @return int ResourceLoaderModule ORIGIN_ class constant
     */
    public function getAllowedModules( $type ) {
        if ( $type == ResourceLoaderModule::TYPE_COMBINED ) {
            return min( array_values( $this->mAllowedModules ) );
        } else {
            return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
        }
    }
    /**
     * Limit the highest level of CSS/JS untrustworthiness allowed.
     *
     * If passed the same or a higher level than the current level of untrustworthiness set, the
     * level will remain unchanged.
     *
     * @param string $type
     * @param int $level ResourceLoaderModule class constant
     */
    public function reduceAllowedModules( $type, $level ) {
        $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
    }
    /**
     * Prepend $text to the body HTML
     *
     * @param string $text HTML
     */
    public function prependHTML( $text ) {
        $this->mBodytext = $text . $this->mBodytext;
    }
    /**
     * Append $text to the body HTML
     *
     * @param string $text HTML
     */
    public function addHTML( $text ) {
        $this->mBodytext .= $text;
    }
    /**
     * Shortcut for adding an Html::element via addHTML.
     *
     * @since 1.19
     *
     * @param string $element
     * @param array $attribs
     * @param string $contents
     */
    public function addElement( $element, array $attribs = [], $contents = '' ) {
        $this->addHTML( Html::element( $element, $attribs, $contents ) );
    }
    /**
     * Clear the body HTML
     */
    public function clearHTML() {
        $this->mBodytext = '';
    }
    /**
     * Get the body HTML
     *
     * @return string HTML
     */
    public function getHTML() {
        return $this->mBodytext;
    }
    /**
     * Get/set the ParserOptions object to use for wikitext parsing
     *
     * @return ParserOptions
     * @suppress PhanUndeclaredProperty For isBogus
     */
    public function parserOptions() {
        if ( !$this->mParserOptions ) {
            if ( !$this->getUser()->isSafeToLoad() ) {
                // $wgUser isn't unstubbable yet, so don't try to get a
                // ParserOptions for it. And don't cache this ParserOptions
                // either.
                $po = ParserOptions::newFromAnon();
                $po->setAllowUnsafeRawHtml( false );
                $po->isBogus = true;
                return $po;
            }
            $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
            $this->mParserOptions->setAllowUnsafeRawHtml( false );
        }
        return $this->mParserOptions;
    }
    /**
     * Set the revision ID which will be seen by the wiki text parser
     * for things such as embedded {{REVISIONID}} variable use.
     *
     * @param int|null $revid A positive integer, or null
     * @return mixed Previous value
     */
    public function setRevisionId( $revid ) {
        $val = $revid === null ? null : intval( $revid );
        return wfSetVar( $this->mRevisionId, $val, true );
    }
    /**
     * Get the displayed revision ID
     *
     * @return int
     */
    public function getRevisionId() {
        return $this->mRevisionId;
    }
    /**
     * Whether the revision displayed is the latest revision of the page
     *
     * @since 1.34
     * @return bool
     */
    public function isRevisionCurrent() {
        return $this->mRevisionId == 0 || $this->mRevisionId == $this->getTitle()->getLatestRevID();
    }
    /**
     * Set the timestamp of the revision which will be displayed. This is used
     * to avoid a extra DB call in Skin::lastModified().
     *
     * @param string|null $timestamp
     * @return mixed Previous value
     */
    public function setRevisionTimestamp( $timestamp ) {
        return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
    }
    /**
     * Get the timestamp of displayed revision.
     * This will be null if not filled by setRevisionTimestamp().
     *
     * @return string|null
     */
    public function getRevisionTimestamp() {
        return $this->mRevisionTimestamp;
    }
    /**
     * Set the displayed file version
     *
     * @param File|null $file
     * @return mixed Previous value
     */
    public function setFileVersion( $file ) {
        $val = null;
        if ( $file instanceof File && $file->exists() ) {
            $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
        }
        return wfSetVar( $this->mFileVersion, $val, true );
    }
    /**
     * Get the displayed file version
     *
     * @return array|null ('time' => MW timestamp, 'sha1' => sha1)
     */
    public function getFileVersion() {
        return $this->mFileVersion;
    }
    /**
     * Get the templates used on this page
     *
     * @return array (namespace => dbKey => revId)
     * @since 1.18
     */
    public function getTemplateIds() {
        return $this->mTemplateIds;
    }
    /**
     * Get the files used on this page
     *
     * @return array [ dbKey => [ 'time' => MW timestamp or null, 'sha1' => sha1 or '' ] ]
     * @since 1.18
     */
    public function getFileSearchOptions() {
        return $this->mImageTimeKeys;
    }
    /**
     * Convert wikitext *in the user interface language* to HTML and
     * add it to the buffer. The result will not be
     * language-converted, as user interface messages are already
     * localized into a specific variant.  Assumes that the current
     * page title will be used if optional $title is not
     * provided. Output will be tidy.
     *
     * @param string $text Wikitext in the user interface language
     * @param bool $linestart Is this the start of a line? (Defaults to true)
     * @param Title|null $title Optional title to use; default of `null`
     *   means use current page title.
     * @throws MWException if $title is not provided and OutputPage::getTitle()
     *   is null
     * @since 1.32
     */
    public function addWikiTextAsInterface(
        $text, $linestart = true, Title $title = null
    ) {
        if ( $title === null ) {
            $title = $this->getTitle();
        }
        if ( !$title ) {
            throw new MWException( 'Title is null' );
        }
        $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/true );
    }
    /**
     * Convert wikitext *in the user interface language* to HTML and
     * add it to the buffer with a `<div class="$wrapperClass">`
     * wrapper.  The result will not be language-converted, as user
     * interface messages as already localized into a specific
     * variant.  The $text will be parsed in start-of-line context.
     * Output will be tidy.
     *
     * @param string $wrapperClass The class attribute value for the <div>
     *   wrapper in the output HTML
     * @param string $text Wikitext in the user interface language
     * @since 1.32
     */
    public function wrapWikiTextAsInterface(
        $wrapperClass, $text
    ) {
        $this->addWikiTextTitleInternal(
            $text, $this->getTitle(),
            /*linestart*/true, /*interface*/true,
            $wrapperClass
        );
    }
    /**
     * Convert wikitext *in the page content language* to HTML and add
     * it to the buffer.  The result with be language-converted to the
     * user's preferred variant.  Assumes that the current page title
     * will be used if optional $title is not provided. Output will be
     * tidy.
     *
     * @param string $text Wikitext in the page content language
     * @param bool $linestart Is this the start of a line? (Defaults to true)
     * @param Title|null $title Optional title to use; default of `null`
     *   means use current page title.
     * @throws MWException if $title is not provided and OutputPage::getTitle()
     *   is null
     * @since 1.32
     */
    public function addWikiTextAsContent(
        $text, $linestart = true, Title $title = null
    ) {
        if ( $title === null ) {
            $title = $this->getTitle();
        }
        if ( !$title ) {
            throw new MWException( 'Title is null' );
        }
        $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/false );
    }
    /**
     * Add wikitext with a custom Title object.
     * Output is unwrapped.
     *
     * @param string $text Wikitext
     * @param Title $title
     * @param bool $linestart Is this the start of a line?
     * @param bool $interface Whether it is an interface message
     *   (for example disables conversion)
     * @param string|null $wrapperClass if not empty, wraps the output in
     *   a `<div class="$wrapperClass">`
     */
    private function addWikiTextTitleInternal(
        $text, Title $title, $linestart, $interface, $wrapperClass = null
    ) {
        $parserOutput = $this->parseInternal(
            $text, $title, $linestart, $interface
        );
        $this->addParserOutput( $parserOutput, [
            'enableSectionEditLinks' => false,
            'wrapperDivClass' => $wrapperClass ?? '',
        ] );
    }
    /**
     * 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,
     * and so on.
     *
     * @since 1.24
     * @param ParserOutput $parserOutput
     */
    public function addParserOutputMetadata( ParserOutput $parserOutput ) {
        $this->mLanguageLinks =
            array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
        $this->addCategoryLinks( $parserOutput->getCategories() );
        $this->setIndicators( $parserOutput->getIndicators() );
        $this->mNewSectionLink = $parserOutput->getNewSection();
        $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
        if ( !$parserOutput->isCacheable() ) {
            $this->enableClientCache( false );
        }
        $this->mNoGallery = $parserOutput->getNoGallery();
        $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
        $this->addModules( $parserOutput->getModules() );
        $this->addModuleStyles( $parserOutput->getModuleStyles() );
        $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
        $this->mPreventClickjacking = $this->mPreventClickjacking
            || $parserOutput->preventClickjacking();
        $scriptSrcs = $parserOutput->getExtraCSPScriptSrcs();
        foreach ( $scriptSrcs as $src ) {
            $this->getCSP()->addScriptSrc( $src );
        }
        $defaultSrcs = $parserOutput->getExtraCSPDefaultSrcs();
        foreach ( $defaultSrcs as $src ) {
            $this->getCSP()->addDefaultSrc( $src );
        }
        $styleSrcs = $parserOutput->getExtraCSPStyleSrcs();
        foreach ( $styleSrcs as $src ) {
            $this->getCSP()->addStyleSrc( $src );
        }
        // If $wgImagePreconnect is true, and if the output contains
        // images, give the user-agent a hint about foreign repos from
        // which those images may be served.  See T123582.
        //
        // TODO: We don't have an easy way to know from which remote(s)
        // the image(s) will be served.  For now, we only hint the first
        // valid one.
        if ( $this->getConfig()->get( 'ImagePreconnect' ) && count( $parserOutput->getImages() ) ) {
            $preconnect = [];
            $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
            $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$preconnect ) {
                $preconnect[] = wfParseUrl( $repo->getZoneUrl( 'thumb' ) )['host'];
            } );
            $preconnect[] = wfParseUrl( $repoGroup->getLocalRepo()->getZoneUrl( 'thumb' ) )['host'];
            foreach ( $preconnect as $host ) {
                if ( $host ) {
                    $this->addLink( [ 'rel' => 'preconnect', 'href' => '//' . $host ] );
                    break;
                }
            }
        }
        // Template versioning...
        foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
            if ( isset( $this->mTemplateIds[$ns] ) ) {
                $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];