MediaWiki REL1_34
ParserOutput.php
Go to the documentation of this file.
1<?php
2
25class ParserOutput extends CacheTime {
33
38
42 public $mText = null;
43
49
54
58 public $mIndicators = [];
59
64
69 public $mLinks = [];
70
75 public $mTemplates = [];
76
81 public $mTemplateIds = [];
82
86 public $mImages = [];
87
92
96 public $mExternalLinks = [];
97
102 public $mInterwikiLinks = [];
103
107 public $mNewSection = false;
108
112 public $mHideNewSection = false;
113
117 public $mNoGallery = false;
118
122 public $mHeadItems = [];
123
127 public $mModules = [];
128
132 public $mModuleStyles = [];
133
137 public $mJsConfigVars = [];
138
142 public $mOutputHooks = [];
143
148 public $mWarnings = [];
149
153 public $mSections = [];
154
158 public $mProperties = [];
159
163 public $mTOCHTML = '';
164
169
173 public $mEnableOOUI = false;
174
178 private $mIndexPolicy = '';
179
183 private $mAccessedOptions = [];
184
188 private $mExtensionData = [];
189
193 private $mLimitReportData = [];
194
197
201 private $mParseStartTime = [];
202
206 private $mPreventClickjacking = false;
207
211 private $mFlags = [];
212
214 private static $speculativeFields = [
215 'speculativePageIdUsed',
216 'mSpeculativeRevId',
217 'revisionTimestampUsed'
218 ];
219
226
229
234
236 private $mMaxAdaptiveExpiry = INF;
237
239 '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#s';
240
241 // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
242 // Current values imply that m=3933.333333 and b=-333.333333
243 // See https://www.nngroup.com/articles/website-response-times/
244 const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
245 const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
246 const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
247 const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
248 const MIN_AR_TTL = 15; // min adaptive TTL (for sanity, pool counter, and edit stashing)
249
259 public function __construct( $text = '', $languageLinks = [], $categoryLinks = [],
260 $unused = false, $titletext = ''
261 ) {
262 $this->mText = $text;
263 $this->mLanguageLinks = $languageLinks;
264 $this->mCategories = $categoryLinks;
265 $this->mTitleText = $titletext;
266 }
267
278 public function hasText() {
279 return ( $this->mText !== null );
280 }
281
290 public function getRawText() {
291 if ( $this->mText === null ) {
292 throw new LogicException( 'This ParserOutput contains no text!' );
293 }
294
295 return $this->mText;
296 }
297
323 public function getText( $options = [] ) {
324 $options += [
325 'allowTOC' => true,
326 'enableSectionEditLinks' => true,
327 'unwrap' => false,
328 'deduplicateStyles' => true,
329 'wrapperDivClass' => $this->getWrapperDivClass(),
330 ];
331 $text = $this->getRawText();
332
333 Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
334
335 if ( $options['wrapperDivClass'] !== '' && !$options['unwrap'] ) {
336 $text = Html::rawElement( 'div', [ 'class' => $options['wrapperDivClass'] ], $text );
337 }
338
339 if ( $options['enableSectionEditLinks'] ) {
340 $text = preg_replace_callback(
341 self::EDITSECTION_REGEX,
342 function ( $m ) {
343 $editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) );
344 $editsectionSection = htmlspecialchars_decode( $m[2] );
345 $editsectionContent = isset( $m[4] ) ? Sanitizer::decodeCharReferences( $m[3] ) : null;
346
347 if ( !is_object( $editsectionPage ) ) {
348 throw new MWException( "Bad parser output text." );
349 }
350
351 $context = RequestContext::getMain();
352 return $context->getSkin()->doEditSectionLink(
353 $editsectionPage,
354 $editsectionSection,
355 $editsectionContent,
356 $context->getLanguage()
357 );
358 },
359 $text
360 );
361 } else {
362 $text = preg_replace( self::EDITSECTION_REGEX, '', $text );
363 }
364
365 if ( $options['allowTOC'] ) {
366 $text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
367 } else {
368 $text = preg_replace(
369 '#' . preg_quote( Parser::TOC_START, '#' ) . '.*?' . preg_quote( Parser::TOC_END, '#' ) . '#s',
370 '',
371 $text
372 );
373 }
374
375 if ( $options['deduplicateStyles'] ) {
376 $seen = [];
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'] ) ) {
382 return $m[0];
383 }
384
385 $key = $attr['data-mw-deduplicate'];
386 if ( !isset( $seen[$key] ) ) {
387 $seen[$key] = true;
388 return $m[0];
389 }
390
391 // We were going to use an empty <style> here, but there
392 // was concern that would be too much overhead for browsers.
393 // So let's hope a <link> with a non-standard rel and href isn't
394 // going to be misinterpreted or mangled by any subsequent processing.
395 return Html::element( 'link', [
396 'rel' => 'mw-deduplicated-inline-style',
397 'href' => "mw-data:" . wfUrlencode( $key ),
398 ] );
399 },
400 $text
401 );
402 }
403
404 // Hydrate slot section header placeholders generated by RevisionRenderer.
405 $text = preg_replace_callback(
406 '#<mw:slotheader>(.*?)</mw:slotheader>#',
407 function ( $m ) {
408 $role = htmlspecialchars_decode( $m[1] );
409 // TODO: map to message, using the interface language. Set lang="xyz" accordingly.
410 $headerText = $role;
411 return $headerText;
412 },
413 $text
414 );
415 return $text;
416 }
417
423 public function addWrapperDivClass( $class ) {
424 $this->mWrapperDivClasses[$class] = true;
425 }
426
431 public function clearWrapperDivClass() {
432 $this->mWrapperDivClasses = [];
433 }
434
442 public function getWrapperDivClass() {
443 return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
444 }
445
450 public function setSpeculativeRevIdUsed( $id ) {
451 $this->mSpeculativeRevId = $id;
452 }
453
458 public function getSpeculativeRevIdUsed() {
459 return $this->mSpeculativeRevId;
460 }
461
466 public function setSpeculativePageIdUsed( $id ) {
467 $this->speculativePageIdUsed = $id;
468 }
469
474 public function getSpeculativePageIdUsed() {
475 return $this->speculativePageIdUsed;
476 }
477
482 public function setRevisionTimestampUsed( $timestamp ) {
483 $this->revisionTimestampUsed = $timestamp;
484 }
485
490 public function getRevisionTimestampUsed() {
491 return $this->revisionTimestampUsed;
492 }
493
498 public function setRevisionUsedSha1Base36( $hash ) {
499 if ( $hash === null ) {
500 return; // e.g. RevisionRecord::getSha1() returned null
501 }
502
503 if (
504 $this->revisionUsedSha1Base36 !== null &&
505 $this->revisionUsedSha1Base36 !== $hash
506 ) {
507 $this->revisionUsedSha1Base36 = ''; // mismatched
508 } else {
509 $this->revisionUsedSha1Base36 = $hash;
510 }
511 }
512
517 public function getRevisionUsedSha1Base36() {
518 return $this->revisionUsedSha1Base36;
519 }
520
521 public function &getLanguageLinks() {
522 return $this->mLanguageLinks;
523 }
524
525 public function getInterwikiLinks() {
526 return $this->mInterwikiLinks;
527 }
528
529 public function getCategoryLinks() {
530 return array_keys( $this->mCategories );
531 }
532
533 public function &getCategories() {
534 return $this->mCategories;
535 }
536
541 public function getIndicators() {
542 return $this->mIndicators;
543 }
544
545 public function getTitleText() {
546 return $this->mTitleText;
547 }
548
549 public function getSections() {
550 return $this->mSections;
551 }
552
553 public function &getLinks() {
554 return $this->mLinks;
555 }
556
557 public function &getTemplates() {
558 return $this->mTemplates;
559 }
560
561 public function &getTemplateIds() {
562 return $this->mTemplateIds;
563 }
564
565 public function &getImages() {
566 return $this->mImages;
567 }
568
569 public function &getFileSearchOptions() {
570 return $this->mFileSearchOptions;
571 }
572
573 public function &getExternalLinks() {
574 return $this->mExternalLinks;
575 }
576
577 public function setNoGallery( $value ) {
578 $this->mNoGallery = (bool)$value;
579 }
580
581 public function getNoGallery() {
582 return $this->mNoGallery;
583 }
584
585 public function getHeadItems() {
586 return $this->mHeadItems;
587 }
588
589 public function getModules() {
590 return $this->mModules;
591 }
592
593 public function getModuleStyles() {
594 return $this->mModuleStyles;
595 }
596
601 public function getJsConfigVars() {
602 return $this->mJsConfigVars;
603 }
604
605 public function getOutputHooks() {
606 return (array)$this->mOutputHooks;
607 }
608
609 public function getWarnings() {
610 return array_keys( $this->mWarnings );
611 }
612
613 public function getIndexPolicy() {
614 return $this->mIndexPolicy;
615 }
616
617 public function getTOCHTML() {
618 return $this->mTOCHTML;
619 }
620
624 public function getTimestamp() {
625 return $this->mTimestamp;
626 }
627
628 public function getLimitReportData() {
629 return $this->mLimitReportData;
630 }
631
632 public function getLimitReportJSData() {
633 return $this->mLimitReportJSData;
634 }
635
636 public function getEnableOOUI() {
637 return $this->mEnableOOUI;
638 }
639
640 public function setText( $text ) {
641 return wfSetVar( $this->mText, $text );
642 }
643
644 public function setLanguageLinks( $ll ) {
645 return wfSetVar( $this->mLanguageLinks, $ll );
646 }
647
648 public function setCategoryLinks( $cl ) {
649 return wfSetVar( $this->mCategories, $cl );
650 }
651
652 public function setTitleText( $t ) {
653 return wfSetVar( $this->mTitleText, $t );
654 }
655
656 public function setSections( $toc ) {
657 return wfSetVar( $this->mSections, $toc );
658 }
659
660 public function setIndexPolicy( $policy ) {
661 return wfSetVar( $this->mIndexPolicy, $policy );
662 }
663
664 public function setTOCHTML( $tochtml ) {
665 return wfSetVar( $this->mTOCHTML, $tochtml );
666 }
667
668 public function setTimestamp( $timestamp ) {
669 return wfSetVar( $this->mTimestamp, $timestamp );
670 }
671
672 public function addCategory( $c, $sort ) {
673 $this->mCategories[$c] = $sort;
674 }
675
681 public function setIndicator( $id, $content ) {
682 $this->mIndicators[$id] = $content;
683 }
684
692 public function setEnableOOUI( $enable = false ) {
693 $this->mEnableOOUI = $enable;
694 }
695
696 public function addLanguageLink( $t ) {
697 $this->mLanguageLinks[] = $t;
698 }
699
700 public function addWarning( $s ) {
701 $this->mWarnings[$s] = 1;
702 }
703
704 public function addOutputHook( $hook, $data = false ) {
705 $this->mOutputHooks[] = [ $hook, $data ];
706 }
707
708 public function setNewSection( $value ) {
709 $this->mNewSection = (bool)$value;
710 }
711
712 public function hideNewSection( $value ) {
713 $this->mHideNewSection = (bool)$value;
714 }
715
716 public function getHideNewSection() {
717 return (bool)$this->mHideNewSection;
718 }
719
720 public function getNewSection() {
721 return (bool)$this->mNewSection;
722 }
723
731 public static function isLinkInternal( $internal, $url ) {
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
737 '(?:[\?\/\#]|$)/i',
738 $url
739 );
740 }
741
742 public function addExternalLink( $url ) {
743 # We don't register links pointing to our own server, unless... :-)
745
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 );
749
750 $registerExternalLink = true;
752 $registerExternalLink = !self::isLinkInternal( $wgServer, $url );
753 }
754 if ( $registerExternalLink ) {
755 $this->mExternalLinks[$url] = 1;
756 }
757 }
758
765 public function addLink( Title $title, $id = null ) {
766 if ( $title->isExternal() ) {
767 // Don't record interwikis in pagelinks
768 $this->addInterwikiLink( $title );
769 return;
770 }
771 $ns = $title->getNamespace();
772 $dbk = $title->getDBkey();
773 if ( $ns == NS_MEDIA ) {
774 // Normalize this pseudo-alias if it makes it down here...
775 $ns = NS_FILE;
776 } elseif ( $ns == NS_SPECIAL ) {
777 // We don't record Special: links currently
778 // It might actually be wise to, but we'd need to do some normalization.
779 return;
780 } elseif ( $dbk === '' ) {
781 // Don't record self links - [[#Foo]]
782 return;
783 }
784 if ( !isset( $this->mLinks[$ns] ) ) {
785 $this->mLinks[$ns] = [];
786 }
787 if ( is_null( $id ) ) {
788 $id = $title->getArticleID();
789 }
790 $this->mLinks[$ns][$dbk] = $id;
791 }
792
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 ];
803 }
804 }
805
812 public function addTemplate( $title, $page_id, $rev_id ) {
813 $ns = $title->getNamespace();
814 $dbk = $title->getDBkey();
815 if ( !isset( $this->mTemplates[$ns] ) ) {
816 $this->mTemplates[$ns] = [];
817 }
818 $this->mTemplates[$ns][$dbk] = $page_id;
819 if ( !isset( $this->mTemplateIds[$ns] ) ) {
820 $this->mTemplateIds[$ns] = [];
821 }
822 $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
823 }
824
829 public function addInterwikiLink( $title ) {
830 if ( !$title->isExternal() ) {
831 throw new MWException( 'Non-interwiki link passed, internal parser error.' );
832 }
833 $prefix = $title->getInterwiki();
834 if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
835 $this->mInterwikiLinks[$prefix] = [];
836 }
837 $this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1;
838 }
839
847 public function addHeadItem( $section, $tag = false ) {
848 if ( $tag !== false ) {
849 $this->mHeadItems[$tag] = $section;
850 } else {
851 $this->mHeadItems[] = $section;
852 }
853 }
854
858 public function addModules( $modules ) {
859 $this->mModules = array_merge( $this->mModules, (array)$modules );
860 }
861
865 public function addModuleStyles( $modules ) {
866 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
867 }
868
876 public function addJsConfigVars( $keys, $value = null ) {
877 if ( is_array( $keys ) ) {
878 foreach ( $keys as $key => $value ) {
879 $this->mJsConfigVars[$key] = $value;
880 }
881 return;
882 }
883
884 $this->mJsConfigVars[$keys] = $value;
885 }
886
892 public function addOutputPageMetadata( OutputPage $out ) {
893 $this->addModules( $out->getModules() );
894 $this->addModuleStyles( $out->getModuleStyles() );
895 $this->addJsConfigVars( $out->getJsConfigVars() );
896
897 $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
898 $this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
899 }
900
917 public function addTrackingCategory( $msg, $title ) {
918 if ( $title->isSpecialPage() ) {
919 wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
920 return false;
921 }
922
923 // Important to parse with correct title (T33469)
924 $cat = wfMessage( $msg )
925 ->title( $title )
926 ->inContentLanguage()
927 ->text();
928
929 # Allow tracking categories to be disabled by setting them to "-"
930 if ( $cat === '-' ) {
931 return false;
932 }
933
934 $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
935 if ( $containerCategory ) {
936 $this->addCategory( $containerCategory->getDBkey(), $this->getProperty( 'defaultsort' ) ?: '' );
937 return true;
938 } else {
939 wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
940 return false;
941 }
942 }
943
955 public function setDisplayTitle( $text ) {
956 $this->setTitleText( $text );
957 $this->setProperty( 'displaytitle', $text );
958 }
959
968 public function getDisplayTitle() {
969 $t = $this->getTitleText();
970 if ( $t === '' ) {
971 return false;
972 }
973 return $t;
974 }
975
981 public function setFlag( $flag ) {
982 $this->mFlags[$flag] = true;
983 }
984
989 public function getFlag( $flag ) {
990 return isset( $this->mFlags[$flag] );
991 }
992
997 public function getAllFlags() {
998 return array_keys( $this->mFlags );
999 }
1000
1061 public function setProperty( $name, $value ) {
1062 $this->mProperties[$name] = $value;
1063 }
1064
1073 public function getProperty( $name ) {
1074 return $this->mProperties[$name] ?? false;
1075 }
1076
1077 public function unsetProperty( $name ) {
1078 unset( $this->mProperties[$name] );
1079 }
1080
1081 public function getProperties() {
1082 if ( !isset( $this->mProperties ) ) {
1083 $this->mProperties = [];
1084 }
1085 return $this->mProperties;
1086 }
1087
1093 public function getUsedOptions() {
1094 if ( !isset( $this->mAccessedOptions ) ) {
1095 return [];
1096 }
1097 return array_keys( $this->mAccessedOptions );
1098 }
1099
1112 public function recordOption( $option ) {
1113 $this->mAccessedOptions[$option] = true;
1114 }
1115
1156 public function setExtensionData( $key, $value ) {
1157 if ( $value === null ) {
1158 unset( $this->mExtensionData[$key] );
1159 } else {
1160 $this->mExtensionData[$key] = $value;
1161 }
1162 }
1163
1175 public function getExtensionData( $key ) {
1176 return $this->mExtensionData[$key] ?? null;
1177 }
1178
1179 private static function getTimes( $clock = null ) {
1180 $ret = [];
1181 if ( !$clock || $clock === 'wall' ) {
1182 $ret['wall'] = microtime( true );
1183 }
1184 if ( !$clock || $clock === 'cpu' ) {
1185 $ru = wfGetRusage();
1186 if ( $ru ) {
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;
1189 }
1190 }
1191 return $ret;
1192 }
1193
1198 public function resetParseStartTime() {
1199 $this->mParseStartTime = self::getTimes();
1200 }
1201
1213 public function getTimeSinceStart( $clock ) {
1214 if ( !isset( $this->mParseStartTime[$clock] ) ) {
1215 return null;
1216 }
1217
1218 $end = self::getTimes( $clock );
1219 return $end[$clock] - $this->mParseStartTime[$clock];
1220 }
1221
1241 public function setLimitReportData( $key, $value ) {
1242 $this->mLimitReportData[$key] = $value;
1243
1244 if ( is_array( $value ) ) {
1245 if ( array_keys( $value ) === [ 0, 1 ]
1246 && is_numeric( $value[0] )
1247 && is_numeric( $value[1] )
1248 ) {
1249 $data = [ 'value' => $value[0], 'limit' => $value[1] ];
1250 } else {
1251 $data = $value;
1252 }
1253 } else {
1254 $data = $value;
1255 }
1256
1257 if ( strpos( $key, '-' ) ) {
1258 list( $ns, $name ) = explode( '-', $key, 2 );
1259 $this->mLimitReportJSData[$ns][$name] = $data;
1260 } else {
1261 $this->mLimitReportJSData[$key] = $data;
1262 }
1263 }
1264
1275 public function hasDynamicContent() {
1277
1278 return $this->getCacheExpiry() < $wgParserCacheExpireTime;
1279 }
1280
1288 public function preventClickjacking( $flag = null ) {
1289 return wfSetVar( $this->mPreventClickjacking, $flag );
1290 }
1291
1298 public function updateRuntimeAdaptiveExpiry( $ttl ) {
1299 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
1300 $this->updateCacheExpiry( $ttl );
1301 }
1302
1309 if ( is_infinite( $this->mMaxAdaptiveExpiry ) ) {
1310 return; // not set
1311 }
1312
1313 $runtime = $this->getTimeSinceStart( 'wall' );
1314 if ( is_float( $runtime ) ) {
1315 $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
1316 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
1317 // SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
1318 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
1319
1320 $adaptiveTTL = min(
1321 max( $slope * $runtime + $point, self::MIN_AR_TTL ),
1322 $this->mMaxAdaptiveExpiry
1323 );
1324 $this->updateCacheExpiry( $adaptiveTTL );
1325 }
1326 }
1327
1328 public function __sleep() {
1329 return array_filter( array_keys( get_object_vars( $this ) ),
1330 function ( $field ) {
1331 if ( $field === 'mParseStartTime' ) {
1332 return false;
1333 } elseif ( strpos( $field, "\0" ) !== false ) {
1334 // Unserializing unknown private fields in HHVM causes
1335 // member variables with nulls in their names (T229366)
1336 return false;
1337 } else {
1338 return true;
1339 }
1340 }
1341 );
1342 }
1343
1352 $this->mOutputHooks = self::mergeList( $this->mOutputHooks, $source->getOutputHooks() );
1353 $this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
1354 $this->mTimestamp = $this->useMaxValue( $this->mTimestamp, $source->getTimestamp() );
1355
1356 foreach ( self::$speculativeFields as $field ) {
1357 if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
1358 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
1359 }
1360 $this->$field = $this->useMaxValue( $this->$field, $source->$field );
1361 }
1362
1363 $this->mParseStartTime = $this->useEachMinValue(
1364 $this->mParseStartTime,
1365 $source->mParseStartTime
1366 );
1367
1368 $this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
1369 $this->mAccessedOptions = self::mergeMap( $this->mAccessedOptions, $source->mAccessedOptions );
1370
1371 // TODO: maintain per-slot limit reports!
1372 if ( empty( $this->mLimitReportData ) ) {
1373 $this->mLimitReportData = $source->mLimitReportData;
1374 }
1375 if ( empty( $this->mLimitReportJSData ) ) {
1376 $this->mLimitReportJSData = $source->mLimitReportJSData;
1377 }
1378 }
1379
1388 // HTML and HTTP
1389 $this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
1390 $this->mModules = self::mergeList( $this->mModules, $source->getModules() );
1391 $this->mModuleStyles = self::mergeList( $this->mModuleStyles, $source->getModuleStyles() );
1392 $this->mJsConfigVars = self::mergeMap( $this->mJsConfigVars, $source->getJsConfigVars() );
1393 $this->mMaxAdaptiveExpiry = min( $this->mMaxAdaptiveExpiry, $source->mMaxAdaptiveExpiry );
1394
1395 // "noindex" always wins!
1396 if ( $this->mIndexPolicy === 'noindex' || $source->mIndexPolicy === 'noindex' ) {
1397 $this->mIndexPolicy = 'noindex';
1398 } elseif ( $this->mIndexPolicy !== 'index' ) {
1399 $this->mIndexPolicy = $source->mIndexPolicy;
1400 }
1401
1402 // Skin control
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();
1408
1409 // TODO: we'll have to be smarter about this!
1410 $this->mSections = array_merge( $this->mSections, $source->getSections() );
1411 $this->mTOCHTML .= $source->mTOCHTML;
1412
1413 // XXX: we don't want to concatenate title text, so first write wins.
1414 // We should use the first *modified* title text, but we don't have the original to check.
1415 if ( $this->mTitleText === null || $this->mTitleText === '' ) {
1416 $this->mTitleText = $source->mTitleText;
1417 }
1418
1419 // class names are stored in array keys
1420 $this->mWrapperDivClasses = self::mergeMap(
1421 $this->mWrapperDivClasses,
1422 $source->mWrapperDivClasses
1423 );
1424
1425 // NOTE: last write wins, same as within one ParserOutput
1426 $this->mIndicators = self::mergeMap( $this->mIndicators, $source->getIndicators() );
1427
1428 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
1429 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
1430 // kinds of extension data to be merged in different ways.
1431 $this->mExtensionData = self::mergeMap(
1432 $this->mExtensionData,
1433 $source->mExtensionData
1434 );
1435 }
1436
1445 $this->mLanguageLinks = self::mergeList( $this->mLanguageLinks, $source->getLanguageLinks() );
1446 $this->mCategories = self::mergeMap( $this->mCategories, $source->getCategories() );
1447 $this->mLinks = self::merge2D( $this->mLinks, $source->getLinks() );
1448 $this->mTemplates = self::merge2D( $this->mTemplates, $source->getTemplates() );
1449 $this->mTemplateIds = self::merge2D( $this->mTemplateIds, $source->getTemplateIds() );
1450 $this->mImages = self::mergeMap( $this->mImages, $source->getImages() );
1451 $this->mFileSearchOptions = self::mergeMap(
1452 $this->mFileSearchOptions,
1453 $source->getFileSearchOptions()
1454 );
1455 $this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
1456 $this->mInterwikiLinks = self::merge2D(
1457 $this->mInterwikiLinks,
1458 $source->getInterwikiLinks()
1459 );
1460
1461 // TODO: add a $mergeStrategy parameter to setProperty to allow different
1462 // kinds of properties to be merged in different ways.
1463 $this->mProperties = self::mergeMap( $this->mProperties, $source->getProperties() );
1464
1465 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
1466 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
1467 // kinds of extension data to be merged in different ways.
1468 $this->mExtensionData = self::mergeMap(
1469 $this->mExtensionData,
1470 $source->mExtensionData
1471 );
1472 }
1473
1474 private static function mergeMixedList( array $a, array $b ) {
1475 return array_unique( array_merge( $a, $b ), SORT_REGULAR );
1476 }
1477
1478 private static function mergeList( array $a, array $b ) {
1479 return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
1480 }
1481
1482 private static function mergeMap( array $a, array $b ) {
1483 return array_replace( $a, $b );
1484 }
1485
1486 private static function merge2D( array $a, array $b ) {
1487 $values = [];
1488 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1489
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] );
1497 } else {
1498 $values[$k] = $b[$k];
1499 }
1500 }
1501
1502 return $values;
1503 }
1504
1505 private static function useEachMinValue( array $a, array $b ) {
1506 $values = [];
1507 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1508
1509 foreach ( $keys as $k ) {
1510 if ( is_array( $a[$k] ?? null ) && is_array( $b[$k] ?? null ) ) {
1511 $values[$k] = self::useEachMinValue( $a[$k], $b[$k] );
1512 } else {
1513 $values[$k] = self::useMinValue( $a[$k] ?? null, $b[$k] ?? null );
1514 }
1515 }
1516
1517 return $values;
1518 }
1519
1520 private static function useMinValue( $a, $b ) {
1521 if ( $a === null ) {
1522 return $b;
1523 }
1524
1525 if ( $b === null ) {
1526 return $a;
1527 }
1528
1529 return min( $a, $b );
1530 }
1531
1532 private static function useMaxValue( $a, $b ) {
1533 if ( $a === null ) {
1534 return $b;
1535 }
1536
1537 if ( $b === null ) {
1538 return $a;
1539 }
1540
1541 return max( $a, $b );
1542 }
1543
1544}
$wgRegisterInternalExternals
By default MediaWiki does not register links pointing to same server in externallinks dataset,...
$wgParserCacheExpireTime
The expiry time for the parser cache, in seconds.
$wgServer
URL of the server.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfGetRusage()
Get system resource usage of current request context.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Parser cache specific expiry check.
Definition CacheTime.php:29
updateCacheExpiry( $seconds)
Sets the number of seconds after which this object should expire.
getCacheExpiry()
Returns the number of seconds after which this object should expire.
MediaWiki exception.
This is one of the Core classes and should be read at least once by any new developers.
getJsConfigVars()
Get the javascript config vars to include on this page.
getHeadItemsArray()
Get an array of head items.
getModules( $filter=false, $position=null, $param='mModules', $type=ResourceLoaderModule::TYPE_COMBINED)
Get the list of modules to include on this page.
getPreventClickjacking()
Get the prevent-clickjacking flag.
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
static mergeMap(array $a, array $b)
addOutputPageMetadata(OutputPage $out)
Copy items from the OutputPage object into this one.
getProperty( $name)
unsetProperty( $name)
static getTimes( $clock=null)
hideNewSection( $value)
int null $mSpeculativeRevId
Assumed rev ID for {{REVISIONID}} if no revision is set.
setNewSection( $value)
addOutputHook( $hook, $data=false)
const SUPPORTS_UNWRAP_TRANSFORM
mergeHtmlMetaDataFrom(ParserOutput $source)
Merges HTML metadata such as head items, JS config vars, and HTTP cache control info from $source int...
static useEachMinValue(array $a, array $b)
setDisplayTitle( $text)
Override the title to be used for display.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
setIndicator( $id, $content)
addExternalLink( $url)
static useMaxValue( $a, $b)
setLanguageLinks( $ll)
setCategoryLinks( $cl)
setRevisionUsedSha1Base36( $hash)
static merge2D(array $a, array $b)
setNoGallery( $value)
hasText()
Returns true if text was passed to the constructor, or set using setText().
static mergeList(array $a, array $b)
addInterwikiLink( $title)
setRevisionTimestampUsed( $timestamp)
setEnableOOUI( $enable=false)
Enables OOUI, if true, in any OutputPage instance this ParserOutput object is added to.
$mWrapperDivClasses
string CSS classes to use for the wrapping div, stored in the array keys.
getDisplayTitle()
Get the title to be used for display.
addWrapperDivClass( $class)
Add a CSS class to use for the wrapping div.
addTrackingCategory( $msg, $title)
Add a tracking category, getting the title from a system message, or print a debug message if the tit...
finalizeAdaptiveCacheExpiry()
Call this when parsing is done to lower the TTL based on low parse times.
addTemplate( $title, $page_id, $rev_id)
Register a template dependency for this output.
addLink(Title $title, $id=null)
Record a local or interwiki inline link for saving in future link tables.
addModules( $modules)
resetParseStartTime()
Resets the parse start timestamps for future calls to getTimeSinceStart()
static mergeMixedList(array $a, array $b)
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
mergeTrackingMetaDataFrom(ParserOutput $source)
Merges dependency tracking metadata such as backlinks, images used, and extension data from $source i...
setProperty( $name, $value)
Set a property to be stored in the page_props database table.
recordOption( $option)
Tags a parser option for use in the cache key for this parser output.
addHeadItem( $section, $tag=false)
Add some text to the "<head>".
hasDynamicContent()
Check whether the cache TTL was lowered due to dynamic content.
static useMinValue( $a, $b)
getExtensionData( $key)
Gets extensions data previously attached to this ParserOutput using setExtensionData().
clearWrapperDivClass()
Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div until addWrap...
getText( $options=[])
Get the output HTML.
getTimeSinceStart( $clock)
Returns the time since resetParseStartTime() was last called.
int null $revisionTimestampUsed
Assumed rev timestamp for {{REVISIONTIMESTAMP}} if no revision is set.
getRawText()
Get the cacheable text with <mw:editsection> markers still in it.
addImage( $name, $timestamp=null, $sha1=null)
Register a file dependency for this output.
updateRuntimeAdaptiveExpiry( $ttl)
Lower the runtime adaptive TTL to at most this value.
setTOCHTML( $tochtml)
getUsedOptions()
Returns the options from its ParserOptions which have been taken into account to produce this output.
const SUPPORTS_STATELESS_TRANSFORMS
Feature flags to indicate to extensions that MediaWiki core supports and uses getText() stateless tra...
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
int null $speculativePageIdUsed
Assumed page ID for {{PAGEID}} if no revision is set.
setExtensionData( $key, $value)
Attaches arbitrary data to this ParserObject.
setTimestamp( $timestamp)
setSpeculativeRevIdUsed( $id)
__construct( $text='', $languageLinks=[], $categoryLinks=[], $unused=false, $titletext='')
setSpeculativePageIdUsed( $id)
static isLinkInternal( $internal, $url)
Checks, if a url is pointing to the own server.
array $mLimitReportJSData
Parser limit report data for JSON.
mergeInternalMetaDataFrom(ParserOutput $source)
Merges internal metadata such as flags, accessed options, and profiling info from $source into this P...
addModuleStyles( $modules)
getWrapperDivClass()
Returns the class (or classes) to be used with the wrapper div for this otuput.
setFlag( $flag)
Attach a flag to the output so that it can be checked later to handle special cases.
static string[] $speculativeFields
const EDITSECTION_REGEX
addCategory( $c, $sort)
setIndexPolicy( $policy)
string null $revisionUsedSha1Base36
SHA-1 base 36 hash of any self-transclusion.
int $mMaxAdaptiveExpiry
Upper bound of expiry based on parse duration.
Represents a title within MediaWiki.
Definition Title.php:42
const NS_FILE
Definition Defines.php:75
const NS_SPECIAL
Definition Defines.php:58
const NS_MEDIA
Definition Defines.php:57
const NS_CATEGORY
Definition Defines.php:83
$context
Definition load.php:45
$source
$sort
$content
Definition router.php:78