215 'speculativePageIdUsed',
217 'revisionTimestampUsed'
239 '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#s';
259 public function __construct( $text =
'', $languageLinks = [], $categoryLinks = [],
260 $unused =
false, $titletext =
''
262 $this->mText = $text;
263 $this->mLanguageLinks = $languageLinks;
264 $this->mCategories = $categoryLinks;
265 $this->mTitleText = $titletext;
279 return ( $this->mText !==
null );
291 if ( $this->mText ===
null ) {
292 throw new LogicException(
'This ParserOutput contains no text!' );
326 'enableSectionEditLinks' =>
true,
328 'deduplicateStyles' =>
true,
335 if ( $options[
'wrapperDivClass'] !==
'' && !$options[
'unwrap'] ) {
336 $text = Html::rawElement(
'div', [
'class' => $options[
'wrapperDivClass'] ], $text );
339 if ( $options[
'enableSectionEditLinks'] ) {
340 $text = preg_replace_callback(
341 self::EDITSECTION_REGEX,
344 $editsectionSection = htmlspecialchars_decode( $m[2] );
345 $editsectionContent = isset( $m[4] ) ? Sanitizer::decodeCharReferences( $m[3] ) :
null;
347 if ( !is_object( $editsectionPage ) ) {
348 throw new MWException(
"Bad parser output text." );
352 return $context->getSkin()->doEditSectionLink(
362 $text = preg_replace( self::EDITSECTION_REGEX,
'', $text );
365 if ( $options[
'allowTOC'] ) {
366 $text = str_replace( [ Parser::TOC_START, Parser::TOC_END ],
'', $text );
368 $text = preg_replace(
369 '#' . preg_quote( Parser::TOC_START,
'#' ) .
'.*?' . preg_quote( Parser::TOC_END,
'#' ) .
'#s',
375 if ( $options[
'deduplicateStyles'] ) {
377 $text = preg_replace_callback(
378 '#<style\s+([^>]*data-mw-deduplicate\s*=[^>]*)>.*?</style>#s',
379 function ( $m ) use ( &$seen ) {
380 $attr = Sanitizer::decodeTagAttributes( $m[1] );
381 if ( !isset( $attr[
'data-mw-deduplicate'] ) ) {
385 $key = $attr[
'data-mw-deduplicate'];
386 if ( !isset( $seen[$key] ) ) {
395 return Html::element(
'link', [
396 'rel' =>
'mw-deduplicated-inline-style',
405 $text = preg_replace_callback(
406 '#<mw:slotheader>(.*?)</mw:slotheader>#',
408 $role = htmlspecialchars_decode( $m[1] );
424 $this->mWrapperDivClasses[$class] =
true;
432 $this->mWrapperDivClasses = [];
443 return implode(
' ', array_keys( $this->mWrapperDivClasses ) );
451 $this->mSpeculativeRevId = $id;
467 $this->speculativePageIdUsed = $id;
483 $this->revisionTimestampUsed = $timestamp;
499 if ( $hash ===
null ) {
504 $this->revisionUsedSha1Base36 !==
null &&
505 $this->revisionUsedSha1Base36 !== $hash
507 $this->revisionUsedSha1Base36 =
'';
509 $this->revisionUsedSha1Base36 = $hash;
530 return array_keys( $this->mCategories );
578 $this->mNoGallery = (bool)$value;
610 return array_keys( $this->mWarnings );
641 return wfSetVar( $this->mText, $text );
645 return wfSetVar( $this->mLanguageLinks, $ll );
649 return wfSetVar( $this->mCategories, $cl );
657 return wfSetVar( $this->mSections, $toc );
661 return wfSetVar( $this->mIndexPolicy, $policy );
665 return wfSetVar( $this->mTOCHTML, $tochtml );
669 return wfSetVar( $this->mTimestamp, $timestamp );
673 $this->mCategories[$c] =
$sort;
693 $this->mEnableOOUI = $enable;
697 $this->mLanguageLinks[] =
$t;
701 $this->mWarnings[
$s] = 1;
705 $this->mOutputHooks[] = [ $hook, $data ];
709 $this->mNewSection = (bool)$value;
713 $this->mHideNewSection = (bool)$value;
732 return (
bool)preg_match(
'/^' .
733 # If server is proto relative, check also
for http/https links
734 ( substr( $internal, 0, 2 ) ===
'//' ?
'(?:https?:)?' :
'' ) .
735 preg_quote( $internal,
'/' ) .
736 # check
for query/path/anchor or end of link in each
case
743 # We don't register links pointing to our own server, unless... :-)
746 # Replace unnecessary URL escape codes with the referenced character
747 # This prevents spammers from hiding links from the filters
748 $url = Parser::normalizeLinkUrl( $url );
750 $registerExternalLink =
true;
754 if ( $registerExternalLink ) {
755 $this->mExternalLinks[$url] = 1;
766 if (
$title->isExternal() ) {
771 $ns =
$title->getNamespace();
772 $dbk =
$title->getDBkey();
780 } elseif ( $dbk ===
'' ) {
784 if ( !isset( $this->mLinks[$ns] ) ) {
785 $this->mLinks[$ns] = [];
787 if ( is_null( $id ) ) {
788 $id =
$title->getArticleID();
790 $this->mLinks[$ns][$dbk] = $id;
799 public function addImage( $name, $timestamp =
null, $sha1 =
null ) {
800 $this->mImages[$name] = 1;
801 if ( $timestamp !==
null && $sha1 !==
null ) {
802 $this->mFileSearchOptions[$name] = [
'time' => $timestamp,
'sha1' => $sha1 ];
813 $ns =
$title->getNamespace();
814 $dbk =
$title->getDBkey();
815 if ( !isset( $this->mTemplates[$ns] ) ) {
816 $this->mTemplates[$ns] = [];
818 $this->mTemplates[$ns][$dbk] = $page_id;
819 if ( !isset( $this->mTemplateIds[$ns] ) ) {
820 $this->mTemplateIds[$ns] = [];
822 $this->mTemplateIds[$ns][$dbk] = $rev_id;
830 if ( !
$title->isExternal() ) {
831 throw new MWException(
'Non-interwiki link passed, internal parser error.' );
833 $prefix =
$title->getInterwiki();
834 if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
835 $this->mInterwikiLinks[$prefix] = [];
837 $this->mInterwikiLinks[$prefix][
$title->getDBkey()] = 1;
848 if ( $tag !==
false ) {
849 $this->mHeadItems[$tag] = $section;
851 $this->mHeadItems[] = $section;
859 $this->mModules = array_merge( $this->mModules, (array)
$modules );
866 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)
$modules );
877 if ( is_array(
$keys ) ) {
878 foreach (
$keys as $key => $value ) {
879 $this->mJsConfigVars[$key] = $value;
884 $this->mJsConfigVars[
$keys] = $value;
897 $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
898 $this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
918 if (
$title->isSpecialPage() ) {
919 wfDebug( __METHOD__ .
": Not adding tracking category $msg to special page!\n" );
926 ->inContentLanguage()
929 # Allow tracking categories to be disabled by setting them to "-"
930 if ( $cat ===
'-' ) {
935 if ( $containerCategory ) {
939 wfDebug( __METHOD__ .
": [[MediaWiki:$msg]] is not a valid title!\n" );
982 $this->mFlags[$flag] =
true;
990 return isset( $this->mFlags[$flag] );
998 return array_keys( $this->mFlags );
1062 $this->mProperties[$name] = $value;
1074 return $this->mProperties[$name] ??
false;
1078 unset( $this->mProperties[$name] );
1082 if ( !isset( $this->mProperties ) ) {
1083 $this->mProperties = [];
1094 if ( !isset( $this->mAccessedOptions ) ) {
1097 return array_keys( $this->mAccessedOptions );
1113 $this->mAccessedOptions[$option] =
true;
1157 if ( $value ===
null ) {
1158 unset( $this->mExtensionData[$key] );
1160 $this->mExtensionData[$key] = $value;
1176 return $this->mExtensionData[$key] ??
null;
1181 if ( !$clock || $clock ===
'wall' ) {
1182 $ret[
'wall'] = microtime(
true );
1184 if ( !$clock || $clock ===
'cpu' ) {
1187 $ret[
'cpu'] = $ru[
'ru_utime.tv_sec'] + $ru[
'ru_utime.tv_usec'] / 1e6;
1188 $ret[
'cpu'] += $ru[
'ru_stime.tv_sec'] + $ru[
'ru_stime.tv_usec'] / 1e6;
1214 if ( !isset( $this->mParseStartTime[$clock] ) ) {
1219 return $end[$clock] - $this->mParseStartTime[$clock];
1242 $this->mLimitReportData[$key] = $value;
1244 if ( is_array( $value ) ) {
1245 if ( array_keys( $value ) === [ 0, 1 ]
1246 && is_numeric( $value[0] )
1247 && is_numeric( $value[1] )
1249 $data = [
'value' => $value[0],
'limit' => $value[1] ];
1257 if ( strpos( $key,
'-' ) ) {
1258 list( $ns, $name ) = explode(
'-', $key, 2 );
1259 $this->mLimitReportJSData[$ns][$name] = $data;
1261 $this->mLimitReportJSData[$key] = $data;
1289 return wfSetVar( $this->mPreventClickjacking, $flag );
1299 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
1309 if ( is_infinite( $this->mMaxAdaptiveExpiry ) ) {
1314 if ( is_float( $runtime ) ) {
1316 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
1318 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
1321 max( $slope * $runtime + $point, self::MIN_AR_TTL ),
1322 $this->mMaxAdaptiveExpiry
1329 return array_filter( array_keys( get_object_vars( $this ) ),
1330 function ( $field ) {
1331 if ( $field ===
'mParseStartTime' ) {
1333 } elseif ( strpos( $field,
"\0" ) !==
false ) {
1354 $this->mTimestamp = $this->
useMaxValue( $this->mTimestamp,
$source->getTimestamp() );
1356 foreach ( self::$speculativeFields as $field ) {
1357 if ( $this->$field &&
$source->$field && $this->$field !==
$source->$field ) {
1358 wfLogWarning( __METHOD__ .
": inconsistent '$field' properties!" );
1364 $this->mParseStartTime,
1372 if ( empty( $this->mLimitReportData ) ) {
1373 $this->mLimitReportData =
$source->mLimitReportData;
1375 if ( empty( $this->mLimitReportJSData ) ) {
1376 $this->mLimitReportJSData =
$source->mLimitReportJSData;
1393 $this->mMaxAdaptiveExpiry = min( $this->mMaxAdaptiveExpiry,
$source->mMaxAdaptiveExpiry );
1396 if ( $this->mIndexPolicy ===
'noindex' ||
$source->mIndexPolicy ===
'noindex' ) {
1397 $this->mIndexPolicy =
'noindex';
1398 } elseif ( $this->mIndexPolicy !==
'index' ) {
1399 $this->mIndexPolicy =
$source->mIndexPolicy;
1403 $this->mNewSection = $this->mNewSection ||
$source->getNewSection();
1404 $this->mHideNewSection = $this->mHideNewSection ||
$source->getHideNewSection();
1405 $this->mNoGallery = $this->mNoGallery ||
$source->getNoGallery();
1406 $this->mEnableOOUI = $this->mEnableOOUI ||
$source->getEnableOOUI();
1407 $this->mPreventClickjacking = $this->mPreventClickjacking ||
$source->preventClickjacking();
1410 $this->mSections = array_merge( $this->mSections,
$source->getSections() );
1411 $this->mTOCHTML .=
$source->mTOCHTML;
1415 if ( $this->mTitleText ===
null || $this->mTitleText ===
'' ) {
1416 $this->mTitleText =
$source->mTitleText;
1421 $this->mWrapperDivClasses,
1432 $this->mExtensionData,
1452 $this->mFileSearchOptions,
1453 $source->getFileSearchOptions()
1457 $this->mInterwikiLinks,
1469 $this->mExtensionData,
1475 return array_unique( array_merge( $a, $b ), SORT_REGULAR );
1479 return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
1482 private static function mergeMap( array $a, array $b ) {
1483 return array_replace( $a, $b );
1486 private static function merge2D( array $a, array $b ) {
1488 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1490 foreach (
$keys as $k ) {
1491 if ( empty( $a[$k] ) ) {
1492 $values[$k] = $b[$k];
1493 } elseif ( empty( $b[$k] ) ) {
1494 $values[$k] = $a[$k];
1495 } elseif ( is_array( $a[$k] ) && is_array( $b[$k] ) ) {
1496 $values[$k] = array_replace( $a[$k], $b[$k] );
1498 $values[$k] = $b[$k];
1507 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1509 foreach (
$keys as $k ) {
1510 if ( is_array( $a[$k] ??
null ) && is_array( $b[$k] ??
null ) ) {
1521 if ( $a ===
null ) {
1525 if ( $b ===
null ) {
1529 return min( $a, $b );
1533 if ( $a ===
null ) {
1537 if ( $b ===
null ) {
1541 return max( $a, $b );