30 const SUPPORTS_STATELESS_TRANSFORMS = 1;
31 const SUPPORTS_UNWRAP_TRANSFORM = 1;
42 public $mLanguageLinks;
52 public $mIndicators = [];
69 public $mTemplates = [];
75 public $mTemplateIds = [];
85 public $mFileSearchOptions = [];
90 public $mExternalLinks = [];
96 public $mInterwikiLinks = [];
101 public $mNewSection =
false;
106 public $mHideNewSection =
false;
111 public $mNoGallery =
false;
116 public $mHeadItems = [];
121 public $mModules = [];
126 public $mModuleScripts = [];
131 public $mModuleStyles = [];
136 public $mJsConfigVars = [];
141 public $mOutputHooks = [];
147 public $mWarnings = [];
152 public $mSections = [];
157 public $mProperties = [];
162 public $mTOCHTML =
'';
172 public $mEnableOOUI =
false;
177 private $mIndexPolicy =
'';
182 private $mAccessedOptions = [];
187 private $mExtensionData = [];
192 private $mLimitReportData = [];
195 private $mLimitReportJSData = [];
200 private $mParseStartTime = [];
205 private $mPreventClickjacking =
false;
210 private $mFlags = [];
213 private $mSpeculativeRevId;
216 private $mMaxAdaptiveExpiry = INF;
218 const EDITSECTION_REGEX =
219 '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#s';
224 const PARSE_FAST_SEC = 0.100;
225 const PARSE_SLOW_SEC = 1.0;
226 const FAST_AR_TTL = 60;
227 const SLOW_AR_TTL = 3600;
228 const MIN_AR_TTL = 15;
230 public function __construct( $text =
'', $languageLinks = [], $categoryLinks = [],
231 $unused =
false, $titletext =
''
233 $this->mText = $text;
234 $this->mLanguageLinks = $languageLinks;
235 $this->mCategories = $categoryLinks;
236 $this->mTitleText = $titletext;
247 public function getRawText() {
270 public function getText(
$options = [] ) {
273 'enableSectionEditLinks' =>
true,
275 'deduplicateStyles' =>
true,
277 $text = $this->mText;
281 if (
$options[
'unwrap'] !==
false ) {
283 'class' =>
'mw-parser-output'
285 $startLen = strlen( $start );
287 $endPos = strrpos( $text, $end );
288 $endLen = strlen( $end );
290 if ( substr( $text, 0, $startLen ) === $start && $endPos !==
false
292 && preg_match(
'/^(?>\s*<!--.*?-->)*\s*$/s', substr( $text, $endPos + $endLen ) )
294 $text = substr( $text, $startLen );
295 $text = substr( $text, 0, $endPos - $startLen )
296 . substr( $text, $endPos - $startLen + $endLen );
300 if (
$options[
'enableSectionEditLinks'] ) {
301 $text = preg_replace_callback(
302 self::EDITSECTION_REGEX,
306 $editsectionSection = htmlspecialchars_decode( $m[2] );
307 $editsectionContent = isset( $m[4] ) ? Sanitizer::decodeCharReferences( $m[3] ) : null;
309 if ( !is_object( $editsectionPage ) ) {
310 throw new MWException(
"Bad parser output text." );
314 return call_user_func_array(
315 [
$skin,
'doEditSectionLink' ],
316 [ $editsectionPage, $editsectionSection,
317 $editsectionContent,
$wgLang->getCode() ]
323 $text = preg_replace( self::EDITSECTION_REGEX,
'', $text );
327 $text = str_replace( [ Parser::TOC_START, Parser::TOC_END ],
'', $text );
329 $text = preg_replace(
330 '#' . preg_quote( Parser::TOC_START,
'#' ) .
'.*?' . preg_quote( Parser::TOC_END,
'#' ) .
'#s',
336 if (
$options[
'deduplicateStyles'] ) {
338 $text = preg_replace_callback(
339 '#<style\s+([^>]*data-mw-deduplicate\s*=[^>]*)>.*?</style>#s',
340 function ( $m )
use ( &$seen ) {
341 $attr = Sanitizer::decodeTagAttributes( $m[1] );
342 if ( !isset( $attr[
'data-mw-deduplicate'] ) ) {
346 $key = $attr[
'data-mw-deduplicate'];
347 if ( !isset( $seen[$key] ) ) {
357 'rel' =>
'mw-deduplicated-inline-style',
372 public function setSpeculativeRevIdUsed( $id ) {
373 $this->mSpeculativeRevId = $id;
380 public function getSpeculativeRevIdUsed() {
381 return $this->mSpeculativeRevId;
384 public function &getLanguageLinks() {
385 return $this->mLanguageLinks;
388 public function getInterwikiLinks() {
389 return $this->mInterwikiLinks;
392 public function getCategoryLinks() {
393 return array_keys( $this->mCategories );
396 public function &getCategories() {
397 return $this->mCategories;
404 public function getIndicators() {
405 return $this->mIndicators;
408 public function getTitleText() {
409 return $this->mTitleText;
412 public function getSections() {
413 return $this->mSections;
419 public function getEditSectionTokens() {
424 public function &getLinks() {
425 return $this->mLinks;
428 public function &getTemplates() {
429 return $this->mTemplates;
432 public function &getTemplateIds() {
433 return $this->mTemplateIds;
436 public function &getImages() {
437 return $this->mImages;
440 public function &getFileSearchOptions() {
441 return $this->mFileSearchOptions;
444 public function &getExternalLinks() {
445 return $this->mExternalLinks;
448 public function getNoGallery() {
449 return $this->mNoGallery;
452 public function getHeadItems() {
453 return $this->mHeadItems;
456 public function getModules() {
457 return $this->mModules;
460 public function getModuleScripts() {
461 return $this->mModuleScripts;
464 public function getModuleStyles() {
465 return $this->mModuleStyles;
472 public function getJsConfigVars() {
473 return $this->mJsConfigVars;
476 public function getOutputHooks() {
477 return (
array)$this->mOutputHooks;
480 public function getWarnings() {
481 return array_keys( $this->mWarnings );
484 public function getIndexPolicy() {
485 return $this->mIndexPolicy;
488 public function getTOCHTML() {
489 return $this->mTOCHTML;
495 public function getTimestamp() {
496 return $this->mTimestamp;
499 public function getLimitReportData() {
500 return $this->mLimitReportData;
503 public function getLimitReportJSData() {
504 return $this->mLimitReportJSData;
510 public function getTOCEnabled() {
515 public function getEnableOOUI() {
516 return $this->mEnableOOUI;
519 public function setText( $text ) {
520 return wfSetVar( $this->mText, $text );
523 public function setLanguageLinks( $ll ) {
524 return wfSetVar( $this->mLanguageLinks, $ll );
527 public function setCategoryLinks( $cl ) {
528 return wfSetVar( $this->mCategories, $cl );
531 public function setTitleText(
$t ) {
535 public function setSections( $toc ) {
536 return wfSetVar( $this->mSections, $toc );
542 public function setEditSectionTokens(
$t ) {
547 public function setIndexPolicy( $policy ) {
548 return wfSetVar( $this->mIndexPolicy, $policy );
551 public function setTOCHTML( $tochtml ) {
552 return wfSetVar( $this->mTOCHTML, $tochtml );
555 public function setTimestamp( $timestamp ) {
556 return wfSetVar( $this->mTimestamp, $timestamp );
562 public function setTOCEnabled( $flag ) {
567 public function addCategory( $c,
$sort ) {
568 $this->mCategories[$c] =
$sort;
576 public function setIndicator( $id, $content ) {
577 $this->mIndicators[$id] = $content;
587 public function setEnableOOUI( $enable =
false ) {
588 $this->mEnableOOUI = $enable;
591 public function addLanguageLink(
$t ) {
592 $this->mLanguageLinks[] =
$t;
595 public function addWarning(
$s ) {
596 $this->mWarnings[
$s] = 1;
599 public function addOutputHook( $hook, $data =
false ) {
600 $this->mOutputHooks[] = [ $hook, $data ];
603 public function setNewSection(
$value ) {
604 $this->mNewSection = (bool)
$value;
606 public function hideNewSection(
$value ) {
607 $this->mHideNewSection = (bool)
$value;
609 public function getHideNewSection() {
610 return (
bool)$this->mHideNewSection;
612 public function getNewSection() {
613 return (
bool)$this->mNewSection;
623 public static function isLinkInternal( $internal, $url ) {
624 return (
bool)preg_match(
'/^' .
626 ( substr( $internal, 0, 2 ) ===
'//' ?
'(?:https?:)?' :
'' ) .
627 preg_quote( $internal,
'/' ) .
634 public function addExternalLink( $url ) {
635 # We don't register links pointing to our own server, unless... :-)
638 # Replace unnecessary URL escape codes with the referenced character
639 # This prevents spammers from hiding links from the filters
640 $url = Parser::normalizeLinkUrl( $url );
642 $registerExternalLink =
true;
644 $registerExternalLink = !self::isLinkInternal(
$wgServer, $url );
646 if ( $registerExternalLink ) {
647 $this->mExternalLinks[$url] = 1;
657 public function addLink(
Title $title, $id =
null ) {
658 if (
$title->isExternal() ) {
660 $this->addInterwikiLink(
$title );
663 $ns =
$title->getNamespace();
664 $dbk =
$title->getDBkey();
672 } elseif ( $dbk ===
'' ) {
676 if ( !isset( $this->mLinks[$ns] ) ) {
677 $this->mLinks[$ns] = [];
679 if ( is_null( $id ) ) {
680 $id =
$title->getArticleID();
682 $this->mLinks[$ns][$dbk] = $id;
692 public function addImage(
$name, $timestamp =
null, $sha1 =
null ) {
693 $this->mImages[
$name] = 1;
694 if ( $timestamp !==
null && $sha1 !==
null ) {
695 $this->mFileSearchOptions[
$name] = [
'time' => $timestamp,
'sha1' => $sha1 ];
706 public function addTemplate(
$title, $page_id, $rev_id ) {
707 $ns =
$title->getNamespace();
708 $dbk =
$title->getDBkey();
709 if ( !isset( $this->mTemplates[$ns] ) ) {
710 $this->mTemplates[$ns] = [];
712 $this->mTemplates[$ns][$dbk] = $page_id;
713 if ( !isset( $this->mTemplateIds[$ns] ) ) {
714 $this->mTemplateIds[$ns] = [];
716 $this->mTemplateIds[$ns][$dbk] = $rev_id;
723 public function addInterwikiLink(
$title ) {
724 if ( !
$title->isExternal() ) {
725 throw new MWException(
'Non-interwiki link passed, internal parser error.' );
727 $prefix =
$title->getInterwiki();
728 if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
729 $this->mInterwikiLinks[$prefix] = [];
731 $this->mInterwikiLinks[$prefix][
$title->getDBkey()] = 1;
741 public function addHeadItem(
$section, $tag =
false ) {
742 if ( $tag !==
false ) {
749 public function addModules(
$modules ) {
750 $this->mModules = array_merge( $this->mModules, (
array)
$modules );
753 public function addModuleScripts(
$modules ) {
754 $this->mModuleScripts = array_merge( $this->mModuleScripts, (
array)
$modules );
757 public function addModuleStyles(
$modules ) {
758 $this->mModuleStyles = array_merge( $this->mModuleStyles, (
array)
$modules );
768 public function addJsConfigVars(
$keys,
$value =
null ) {
769 if ( is_array(
$keys ) ) {
771 $this->mJsConfigVars[$key] =
$value;
785 $this->addModules(
$out->getModules() );
786 $this->addModuleScripts(
$out->getModuleScripts() );
787 $this->addModuleStyles(
$out->getModuleStyles() );
788 $this->addJsConfigVars(
$out->getJsConfigVars() );
790 $this->mHeadItems = array_merge( $this->mHeadItems,
$out->getHeadItemsArray() );
791 $this->mPreventClickjacking = $this->mPreventClickjacking ||
$out->getPreventClickjacking();
810 public function addTrackingCategory( $msg,
$title ) {
811 if (
$title->isSpecialPage() ) {
812 wfDebug( __METHOD__ .
": Not adding tracking category $msg to special page!\n" );
819 ->inContentLanguage()
822 # Allow tracking categories to be disabled by setting them to "-"
823 if ( $cat ===
'-' ) {
828 if ( $containerCategory ) {
829 $this->addCategory( $containerCategory->getDBkey(), $this->getProperty(
'defaultsort' ) ?:
'' );
832 wfDebug( __METHOD__ .
": [[MediaWiki:$msg]] is not a valid title!\n" );
848 public function setDisplayTitle( $text ) {
849 $this->setTitleText( $text );
850 $this->setProperty(
'displaytitle', $text );
861 public function getDisplayTitle() {
862 $t = $this->getTitleText();
873 public function setFlag( $flag ) {
874 $this->mFlags[$flag] =
true;
877 public function getFlag( $flag ) {
878 return isset( $this->mFlags[$flag] );
954 public function getProperty(
$name ) {
955 return isset( $this->mProperties[
$name] ) ? $this->mProperties[
$name] :
false;
958 public function unsetProperty(
$name ) {
959 unset( $this->mProperties[
$name] );
962 public function getProperties() {
963 if ( !isset( $this->mProperties ) ) {
964 $this->mProperties = [];
966 return $this->mProperties;
974 public function getUsedOptions() {
975 if ( !isset( $this->mAccessedOptions ) ) {
978 return array_keys( $this->mAccessedOptions );
993 public function recordOption( $option ) {
994 $this->mAccessedOptions[$option] =
true;
1037 public function setExtensionData( $key,
$value ) {
1039 unset( $this->mExtensionData[$key] );
1041 $this->mExtensionData[$key] =
$value;
1056 public function getExtensionData( $key ) {
1057 if ( isset( $this->mExtensionData[$key] ) ) {
1058 return $this->mExtensionData[$key];
1064 private static function getTimes( $clock =
null ) {
1066 if ( !$clock || $clock ===
'wall' ) {
1067 $ret[
'wall'] = microtime(
true );
1069 if ( !$clock || $clock ===
'cpu' ) {
1072 $ret[
'cpu'] = $ru[
'ru_utime.tv_sec'] + $ru[
'ru_utime.tv_usec'] / 1e6;
1073 $ret[
'cpu'] += $ru[
'ru_stime.tv_sec'] + $ru[
'ru_stime.tv_usec'] / 1e6;
1083 public function resetParseStartTime() {
1084 $this->mParseStartTime = self::getTimes();
1098 public function getTimeSinceStart( $clock ) {
1099 if ( !isset( $this->mParseStartTime[$clock] ) ) {
1103 $end = self::getTimes( $clock );
1104 return $end[$clock] - $this->mParseStartTime[$clock];
1126 public function setLimitReportData( $key,
$value ) {
1127 $this->mLimitReportData[$key] =
$value;
1129 if ( is_array(
$value ) ) {
1130 if ( array_keys(
$value ) === [ 0, 1 ]
1131 && is_numeric(
$value[0] )
1132 && is_numeric(
$value[1] )
1134 $data = [
'value' =>
$value[0],
'limit' =>
$value[1] ];
1142 if ( strpos( $key,
'-' ) ) {
1143 list( $ns,
$name ) = explode(
'-', $key, 2 );
1144 $this->mLimitReportJSData[$ns][
$name] = $data;
1146 $this->mLimitReportJSData[$key] = $data;
1160 public function hasDynamicContent() {
1173 public function preventClickjacking( $flag =
null ) {
1174 return wfSetVar( $this->mPreventClickjacking, $flag );
1183 public function updateRuntimeAdaptiveExpiry( $ttl ) {
1184 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
1193 public function finalizeAdaptiveCacheExpiry() {
1194 if ( is_infinite( $this->mMaxAdaptiveExpiry ) ) {
1198 $runtime = $this->getTimeSinceStart(
'wall' );
1199 if ( is_float( $runtime ) ) {
1200 $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
1201 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
1203 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
1206 max( $slope * $runtime + $point, self::MIN_AR_TTL ),
1207 $this->mMaxAdaptiveExpiry
1213 public function __sleep() {
1215 array_keys( get_object_vars( $this ) ),
1216 [
'mParseStartTime' ]