MediaWiki REL1_35
ParserOutput.php
Go to the documentation of this file.
1<?php
2
4
27class ParserOutput extends CacheTime {
35
39 public const SUPPORTS_UNWRAP_TRANSFORM = 1;
40
44 public $mText = null;
45
51
56
60 public $mIndicators = [];
61
66
72 public $mLinks = [];
73
78 public $mLinksSpecial = [];
79
84 public $mTemplates = [];
85
90 public $mTemplateIds = [];
91
95 public $mImages = [];
96
101
105 public $mExternalLinks = [];
106
111 public $mInterwikiLinks = [];
112
116 public $mNewSection = false;
117
121 public $mHideNewSection = false;
122
126 public $mNoGallery = false;
127
131 public $mHeadItems = [];
132
136 public $mModules = [];
137
141 public $mModuleStyles = [];
142
146 public $mJsConfigVars = [];
147
151 public $mOutputHooks = [];
152
157 public $mWarnings = [];
158
162 public $mSections = [];
163
167 public $mProperties = [];
168
172 public $mTOCHTML = '';
173
178
182 public $mEnableOOUI = false;
183
187 private $mIndexPolicy = '';
188
192 private $mAccessedOptions = [];
193
197 private $mExtensionData = [];
198
202 private $mLimitReportData = [];
203
206
210 private $mParseStartTime = [];
211
215 private $mPreventClickjacking = false;
216
220 private $mExtraScriptSrcs = [];
221
225 private $mExtraDefaultSrcs = [];
226
230 private $mExtraStyleSrcs = [];
231
235 private $mFlags = [];
236
238 private const SPECULATIVE_FIELDS = [
239 'speculativePageIdUsed',
240 'mSpeculativeRevId',
241 'revisionTimestampUsed'
242 ];
243
250
253
258
260 private $mMaxAdaptiveExpiry = INF;
261
262 private const EDITSECTION_REGEX =
263 '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#s';
264
265 // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
266 // Current values imply that m=3933.333333 and b=-333.333333
267 // See https://www.nngroup.com/articles/website-response-times/
268 private const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
269 private const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
270 private const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
271 private const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
272 private const MIN_AR_TTL = 15; // min adaptive TTL (for sanity, pool counter, and edit stashing)
273
283 public function __construct( $text = '', $languageLinks = [], $categoryLinks = [],
284 $unused = false, $titletext = ''
285 ) {
286 $this->mText = $text;
287 $this->mLanguageLinks = $languageLinks;
288 $this->mCategories = $categoryLinks;
289 $this->mTitleText = $titletext;
290 }
291
302 public function hasText() {
303 return ( $this->mText !== null );
304 }
305
314 public function getRawText() {
315 if ( $this->mText === null ) {
316 throw new LogicException( 'This ParserOutput contains no text!' );
317 }
318
319 return $this->mText;
320 }
321
348 public function getText( $options = [] ) {
349 $options += [
350 'allowTOC' => true,
351 'enableSectionEditLinks' => true,
352 'skin' => null,
353 'unwrap' => false,
354 'deduplicateStyles' => true,
355 'wrapperDivClass' => $this->getWrapperDivClass(),
356 ];
357 $text = $this->getRawText();
358
359 Hooks::runner()->onParserOutputPostCacheTransform( $this, $text, $options );
360
361 if ( $options['wrapperDivClass'] !== '' && !$options['unwrap'] ) {
362 $text = Html::rawElement( 'div', [ 'class' => $options['wrapperDivClass'] ], $text );
363 }
364
365 if ( $options['enableSectionEditLinks'] ) {
366 // TODO: Passing the skin should be required
367 $skin = $options['skin'] ?: RequestContext::getMain()->getSkin();
368
369 $text = preg_replace_callback(
370 self::EDITSECTION_REGEX,
371 function ( $m ) use ( $skin ) {
372 $editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) );
373 $editsectionSection = htmlspecialchars_decode( $m[2] );
374 $editsectionContent = isset( $m[4] ) ? Sanitizer::decodeCharReferences( $m[3] ) : null;
375
376 if ( !is_object( $editsectionPage ) ) {
377 LoggerFactory::getInstance( 'Parser' )
378 ->error(
379 'ParserOutput::getText(): bad title in editsection placeholder',
380 [
381 'placeholder' => $m[0],
382 'editsectionPage' => $m[1],
383 'titletext' => $this->getTitleText(),
384 'phab' => 'T261347'
385 ]
386 );
387 return '';
388 }
389
390 return $skin->doEditSectionLink(
391 $editsectionPage,
392 $editsectionSection,
393 $editsectionContent,
394 $skin->getLanguage()
395 );
396 },
397 $text
398 );
399 } else {
400 $text = preg_replace( self::EDITSECTION_REGEX, '', $text );
401 }
402
403 if ( $options['allowTOC'] ) {
404 $text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
405 } else {
406 $text = preg_replace(
407 '#' . preg_quote( Parser::TOC_START, '#' ) . '.*?' . preg_quote( Parser::TOC_END, '#' ) . '#s',
408 '',
409 $text
410 );
411 }
412
413 if ( $options['deduplicateStyles'] ) {
414 $seen = [];
415 $text = preg_replace_callback(
416 '#<style\s+([^>]*data-mw-deduplicate\s*=[^>]*)>.*?</style>#s',
417 function ( $m ) use ( &$seen ) {
418 $attr = Sanitizer::decodeTagAttributes( $m[1] );
419 if ( !isset( $attr['data-mw-deduplicate'] ) ) {
420 return $m[0];
421 }
422
423 $key = $attr['data-mw-deduplicate'];
424 if ( !isset( $seen[$key] ) ) {
425 $seen[$key] = true;
426 return $m[0];
427 }
428
429 // We were going to use an empty <style> here, but there
430 // was concern that would be too much overhead for browsers.
431 // So let's hope a <link> with a non-standard rel and href isn't
432 // going to be misinterpreted or mangled by any subsequent processing.
433 return Html::element( 'link', [
434 'rel' => 'mw-deduplicated-inline-style',
435 'href' => "mw-data:" . wfUrlencode( $key ),
436 ] );
437 },
438 $text
439 );
440 }
441
442 // Hydrate slot section header placeholders generated by RevisionRenderer.
443 $text = preg_replace_callback(
444 '#<mw:slotheader>(.*?)</mw:slotheader>#',
445 function ( $m ) {
446 $role = htmlspecialchars_decode( $m[1] );
447 // TODO: map to message, using the interface language. Set lang="xyz" accordingly.
448 $headerText = $role;
449 return $headerText;
450 },
451 $text
452 );
453 return $text;
454 }
455
461 public function addWrapperDivClass( $class ) {
462 $this->mWrapperDivClasses[$class] = true;
463 }
464
469 public function clearWrapperDivClass() {
470 $this->mWrapperDivClasses = [];
471 }
472
480 public function getWrapperDivClass() {
481 return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
482 }
483
488 public function setSpeculativeRevIdUsed( $id ) {
489 $this->mSpeculativeRevId = $id;
490 }
491
496 public function getSpeculativeRevIdUsed() {
497 return $this->mSpeculativeRevId;
498 }
499
504 public function setSpeculativePageIdUsed( $id ) {
505 $this->speculativePageIdUsed = $id;
506 }
507
512 public function getSpeculativePageIdUsed() {
513 return $this->speculativePageIdUsed;
514 }
515
520 public function setRevisionTimestampUsed( $timestamp ) {
521 $this->revisionTimestampUsed = $timestamp;
522 }
523
528 public function getRevisionTimestampUsed() {
529 return $this->revisionTimestampUsed;
530 }
531
536 public function setRevisionUsedSha1Base36( $hash ) {
537 if ( $hash === null ) {
538 return; // e.g. RevisionRecord::getSha1() returned null
539 }
540
541 if (
542 $this->revisionUsedSha1Base36 !== null &&
543 $this->revisionUsedSha1Base36 !== $hash
544 ) {
545 $this->revisionUsedSha1Base36 = ''; // mismatched
546 } else {
547 $this->revisionUsedSha1Base36 = $hash;
548 }
549 }
550
555 public function getRevisionUsedSha1Base36() {
556 return $this->revisionUsedSha1Base36;
557 }
558
559 public function &getLanguageLinks() {
560 return $this->mLanguageLinks;
561 }
562
563 public function getInterwikiLinks() {
564 return $this->mInterwikiLinks;
565 }
566
567 public function getCategoryLinks() {
568 return array_keys( $this->mCategories );
569 }
570
571 public function &getCategories() {
572 return $this->mCategories;
573 }
574
579 public function getIndicators() {
580 return $this->mIndicators;
581 }
582
583 public function getTitleText() {
584 return $this->mTitleText;
585 }
586
587 public function getSections() {
588 return $this->mSections;
589 }
590
591 public function &getLinks() {
592 return $this->mLinks;
593 }
594
599 public function &getLinksSpecial() {
600 return $this->mLinksSpecial;
601 }
602
603 public function &getTemplates() {
604 return $this->mTemplates;
605 }
606
607 public function &getTemplateIds() {
608 return $this->mTemplateIds;
609 }
610
611 public function &getImages() {
612 return $this->mImages;
613 }
614
615 public function &getFileSearchOptions() {
616 return $this->mFileSearchOptions;
617 }
618
619 public function &getExternalLinks() {
620 return $this->mExternalLinks;
621 }
622
623 public function setNoGallery( $value ) {
624 $this->mNoGallery = (bool)$value;
625 }
626
627 public function getNoGallery() {
628 return $this->mNoGallery;
629 }
630
631 public function getHeadItems() {
632 return $this->mHeadItems;
633 }
634
635 public function getModules() {
636 return $this->mModules;
637 }
638
639 public function getModuleStyles() {
640 return $this->mModuleStyles;
641 }
642
647 public function getJsConfigVars() {
648 return $this->mJsConfigVars;
649 }
650
651 public function getOutputHooks() {
652 return (array)$this->mOutputHooks;
653 }
654
655 public function getWarnings() {
656 return array_keys( $this->mWarnings );
657 }
658
659 public function getIndexPolicy() {
660 return $this->mIndexPolicy;
661 }
662
663 public function getTOCHTML() {
664 return $this->mTOCHTML;
665 }
666
670 public function getTimestamp() {
671 return $this->mTimestamp;
672 }
673
674 public function getLimitReportData() {
675 return $this->mLimitReportData;
676 }
677
678 public function getLimitReportJSData() {
679 return $this->mLimitReportJSData;
680 }
681
682 public function getEnableOOUI() {
683 return $this->mEnableOOUI;
684 }
685
691 public function getExtraCSPDefaultSrcs() {
692 return $this->mExtraDefaultSrcs;
693 }
694
700 public function getExtraCSPScriptSrcs() {
701 return $this->mExtraScriptSrcs;
702 }
703
709 public function getExtraCSPStyleSrcs() {
710 return $this->mExtraStyleSrcs;
711 }
712
713 public function setText( $text ) {
714 return wfSetVar( $this->mText, $text );
715 }
716
717 public function setLanguageLinks( $ll ) {
718 return wfSetVar( $this->mLanguageLinks, $ll );
719 }
720
721 public function setCategoryLinks( $cl ) {
722 return wfSetVar( $this->mCategories, $cl );
723 }
724
725 public function setTitleText( $t ) {
726 return wfSetVar( $this->mTitleText, $t );
727 }
728
729 public function setSections( $toc ) {
730 return wfSetVar( $this->mSections, $toc );
731 }
732
733 public function setIndexPolicy( $policy ) {
734 return wfSetVar( $this->mIndexPolicy, $policy );
735 }
736
737 public function setTOCHTML( $tochtml ) {
738 return wfSetVar( $this->mTOCHTML, $tochtml );
739 }
740
741 public function setTimestamp( $timestamp ) {
742 return wfSetVar( $this->mTimestamp, $timestamp );
743 }
744
745 public function addCategory( $c, $sort ) {
746 $this->mCategories[$c] = $sort;
747 }
748
754 public function setIndicator( $id, $content ) {
755 $this->mIndicators[$id] = $content;
756 }
757
765 public function setEnableOOUI( $enable = false ) {
766 $this->mEnableOOUI = $enable;
767 }
768
769 public function addLanguageLink( $t ) {
770 $this->mLanguageLinks[] = $t;
771 }
772
773 public function addWarning( $s ) {
774 $this->mWarnings[$s] = 1;
775 }
776
777 public function addOutputHook( $hook, $data = false ) {
778 $this->mOutputHooks[] = [ $hook, $data ];
779 }
780
781 public function setNewSection( $value ) {
782 $this->mNewSection = (bool)$value;
783 }
784
785 public function hideNewSection( $value ) {
786 $this->mHideNewSection = (bool)$value;
787 }
788
789 public function getHideNewSection() {
790 return (bool)$this->mHideNewSection;
791 }
792
793 public function getNewSection() {
794 return (bool)$this->mNewSection;
795 }
796
804 public static function isLinkInternal( $internal, $url ) {
805 return (bool)preg_match( '/^' .
806 # If server is proto relative, check also for http/https links
807 ( substr( $internal, 0, 2 ) === '//' ? '(?:https?:)?' : '' ) .
808 preg_quote( $internal, '/' ) .
809 # check for query/path/anchor or end of link in each case
810 '(?:[\?\/\#]|$)/i',
811 $url
812 );
813 }
814
815 public function addExternalLink( $url ) {
816 # We don't register links pointing to our own server, unless... :-)
818
819 # Replace unnecessary URL escape codes with the referenced character
820 # This prevents spammers from hiding links from the filters
821 $url = Parser::normalizeLinkUrl( $url );
822
823 $registerExternalLink = true;
825 $registerExternalLink = !self::isLinkInternal( $wgServer, $url );
826 }
827 if ( $registerExternalLink ) {
828 $this->mExternalLinks[$url] = 1;
829 }
830 }
831
838 public function addLink( Title $title, $id = null ) {
839 if ( $title->isExternal() ) {
840 // Don't record interwikis in pagelinks
841 $this->addInterwikiLink( $title );
842 return;
843 }
844 $ns = $title->getNamespace();
845 $dbk = $title->getDBkey();
846 if ( $ns == NS_MEDIA ) {
847 // Normalize this pseudo-alias if it makes it down here...
848 $ns = NS_FILE;
849 } elseif ( $ns == NS_SPECIAL ) {
850 // We don't want to record Special: links in the database, so put them in a separate place.
851 // It might actually be wise to, but we'd need to do some normalization.
852 $this->mLinksSpecial[$dbk] = 1;
853 return;
854 } elseif ( $dbk === '' ) {
855 // Don't record self links - [[#Foo]]
856 return;
857 }
858 if ( !isset( $this->mLinks[$ns] ) ) {
859 $this->mLinks[$ns] = [];
860 }
861 if ( $id === null ) {
862 $id = $title->getArticleID();
863 }
864 $this->mLinks[$ns][$dbk] = $id;
865 }
866
873 public function addImage( $name, $timestamp = null, $sha1 = null ) {
874 $this->mImages[$name] = 1;
875 if ( $timestamp !== null && $sha1 !== null ) {
876 $this->mFileSearchOptions[$name] = [ 'time' => $timestamp, 'sha1' => $sha1 ];
877 }
878 }
879
886 public function addTemplate( $title, $page_id, $rev_id ) {
887 $ns = $title->getNamespace();
888 $dbk = $title->getDBkey();
889 if ( !isset( $this->mTemplates[$ns] ) ) {
890 $this->mTemplates[$ns] = [];
891 }
892 $this->mTemplates[$ns][$dbk] = $page_id;
893 if ( !isset( $this->mTemplateIds[$ns] ) ) {
894 $this->mTemplateIds[$ns] = [];
895 }
896 $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
897 }
898
903 public function addInterwikiLink( $title ) {
904 if ( !$title->isExternal() ) {
905 throw new MWException( 'Non-interwiki link passed, internal parser error.' );
906 }
907 $prefix = $title->getInterwiki();
908 if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
909 $this->mInterwikiLinks[$prefix] = [];
910 }
911 $this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1;
912 }
913
921 public function addHeadItem( $section, $tag = false ) {
922 if ( $tag !== false ) {
923 $this->mHeadItems[$tag] = $section;
924 } else {
925 $this->mHeadItems[] = $section;
926 }
927 }
928
933 public function addModules( $modules ) {
934 $this->mModules = array_merge( $this->mModules, (array)$modules );
935 }
936
941 public function addModuleStyles( $modules ) {
942 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
943 }
944
952 public function addJsConfigVars( $keys, $value = null ) {
953 if ( is_array( $keys ) ) {
954 foreach ( $keys as $key => $value ) {
955 $this->mJsConfigVars[$key] = $value;
956 }
957 return;
958 }
959
960 $this->mJsConfigVars[$keys] = $value;
961 }
962
968 public function addOutputPageMetadata( OutputPage $out ) {
969 $this->addModules( $out->getModules() );
970 $this->addModuleStyles( $out->getModuleStyles() );
971 $this->addJsConfigVars( $out->getJsConfigVars() );
972
973 $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
974 $this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
975 }
976
993 public function addTrackingCategory( $msg, $title ) {
994 if ( $title->isSpecialPage() ) {
995 wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!" );
996 return false;
997 }
998
999 // Important to parse with correct title (T33469)
1000 $cat = wfMessage( $msg )
1001 ->title( $title )
1002 ->inContentLanguage()
1003 ->text();
1004
1005 # Allow tracking categories to be disabled by setting them to "-"
1006 if ( $cat === '-' ) {
1007 return false;
1008 }
1009
1010 $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
1011 if ( $containerCategory ) {
1012 $this->addCategory( $containerCategory->getDBkey(), $this->getProperty( 'defaultsort' ) ?: '' );
1013 return true;
1014 } else {
1015 wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!" );
1016 return false;
1017 }
1018 }
1019
1031 public function setDisplayTitle( $text ) {
1032 $this->setTitleText( $text );
1033 $this->setProperty( 'displaytitle', $text );
1034 }
1035
1044 public function getDisplayTitle() {
1045 $t = $this->getTitleText();
1046 if ( $t === '' ) {
1047 return false;
1048 }
1049 return $t;
1050 }
1051
1057 public function setFlag( $flag ) {
1058 $this->mFlags[$flag] = true;
1059 }
1060
1065 public function getFlag( $flag ) {
1066 return isset( $this->mFlags[$flag] );
1067 }
1068
1073 public function getAllFlags() {
1074 return array_keys( $this->mFlags );
1075 }
1076
1137 public function setProperty( $name, $value ) {
1138 $this->mProperties[$name] = $value;
1139 }
1140
1149 public function getProperty( $name ) {
1150 return $this->mProperties[$name] ?? false;
1151 }
1152
1153 public function unsetProperty( $name ) {
1154 unset( $this->mProperties[$name] );
1155 }
1156
1157 public function getProperties() {
1158 if ( !isset( $this->mProperties ) ) {
1159 $this->mProperties = [];
1160 }
1161 return $this->mProperties;
1162 }
1163
1169 public function getUsedOptions() {
1170 if ( !isset( $this->mAccessedOptions ) ) {
1171 return [];
1172 }
1173 return array_keys( $this->mAccessedOptions );
1174 }
1175
1188 public function recordOption( $option ) {
1189 $this->mAccessedOptions[$option] = true;
1190 }
1191
1232 public function setExtensionData( $key, $value ) {
1233 if ( $value === null ) {
1234 unset( $this->mExtensionData[$key] );
1235 } else {
1236 $this->mExtensionData[$key] = $value;
1237 }
1238 }
1239
1251 public function getExtensionData( $key ) {
1252 return $this->mExtensionData[$key] ?? null;
1253 }
1254
1255 private static function getTimes( $clock = null ) {
1256 $ret = [];
1257 if ( !$clock || $clock === 'wall' ) {
1258 $ret['wall'] = microtime( true );
1259 }
1260 if ( !$clock || $clock === 'cpu' ) {
1261 $ru = getrusage( 0 /* RUSAGE_SELF */ );
1262 $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
1263 $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
1264 }
1265 return $ret;
1266 }
1267
1272 public function resetParseStartTime() {
1273 $this->mParseStartTime = self::getTimes();
1274 }
1275
1287 public function getTimeSinceStart( $clock ) {
1288 if ( !isset( $this->mParseStartTime[$clock] ) ) {
1289 return null;
1290 }
1291
1292 $end = self::getTimes( $clock );
1293 return $end[$clock] - $this->mParseStartTime[$clock];
1294 }
1295
1315 public function setLimitReportData( $key, $value ) {
1316 $this->mLimitReportData[$key] = $value;
1317
1318 if ( is_array( $value ) ) {
1319 if ( array_keys( $value ) === [ 0, 1 ]
1320 && is_numeric( $value[0] )
1321 && is_numeric( $value[1] )
1322 ) {
1323 $data = [ 'value' => $value[0], 'limit' => $value[1] ];
1324 } else {
1325 $data = $value;
1326 }
1327 } else {
1328 $data = $value;
1329 }
1330
1331 if ( strpos( $key, '-' ) ) {
1332 list( $ns, $name ) = explode( '-', $key, 2 );
1333 $this->mLimitReportJSData[$ns][$name] = $data;
1334 } else {
1335 $this->mLimitReportJSData[$key] = $data;
1336 }
1337 }
1338
1349 public function hasDynamicContent() {
1351
1352 return $this->getCacheExpiry() < $wgParserCacheExpireTime;
1353 }
1354
1362 public function preventClickjacking( $flag = null ) {
1363 return wfSetVar( $this->mPreventClickjacking, $flag );
1364 }
1365
1372 public function updateRuntimeAdaptiveExpiry( $ttl ) {
1373 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
1374 $this->updateCacheExpiry( $ttl );
1375 }
1376
1386 public function addExtraCSPDefaultSrc( $src ) {
1387 $this->mExtraDefaultSrcs[] = $src;
1388 }
1389
1396 public function addExtraCSPStyleSrc( $src ) {
1397 $this->mExtraStyleSrcs[] = $src;
1398 }
1399
1408 public function addExtraCSPScriptSrc( $src ) {
1409 $this->mExtraScriptSrcs[] = $src;
1410 }
1411
1418 if ( is_infinite( $this->mMaxAdaptiveExpiry ) ) {
1419 return; // not set
1420 }
1421
1422 $runtime = $this->getTimeSinceStart( 'wall' );
1423 if ( is_float( $runtime ) ) {
1424 $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
1425 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
1426 // SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
1427 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
1428
1429 $adaptiveTTL = min(
1430 max( $slope * $runtime + $point, self::MIN_AR_TTL ),
1431 $this->mMaxAdaptiveExpiry
1432 );
1433 $this->updateCacheExpiry( $adaptiveTTL );
1434 }
1435 }
1436
1437 public function __sleep() {
1438 return array_filter( array_keys( get_object_vars( $this ) ),
1439 function ( $field ) {
1440 if ( $field === 'mParseStartTime' ) {
1441 return false;
1442 } elseif ( strpos( $field, "\0" ) !== false ) {
1443 // Unserializing unknown private fields in HHVM causes
1444 // member variables with nulls in their names (T229366)
1445 return false;
1446 } else {
1447 return true;
1448 }
1449 }
1450 );
1451 }
1452
1461 $this->mOutputHooks = self::mergeList( $this->mOutputHooks, $source->getOutputHooks() );
1462 $this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
1463 $this->mTimestamp = $this->useMaxValue( $this->mTimestamp, $source->getTimestamp() );
1464
1465 foreach ( self::SPECULATIVE_FIELDS as $field ) {
1466 if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
1467 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
1468 }
1469 $this->$field = $this->useMaxValue( $this->$field, $source->$field );
1470 }
1471
1472 $this->mParseStartTime = $this->useEachMinValue(
1473 $this->mParseStartTime,
1474 $source->mParseStartTime
1475 );
1476
1477 $this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
1478 $this->mAccessedOptions = self::mergeMap( $this->mAccessedOptions, $source->mAccessedOptions );
1479
1480 // TODO: maintain per-slot limit reports!
1481 if ( empty( $this->mLimitReportData ) ) {
1482 $this->mLimitReportData = $source->mLimitReportData;
1483 }
1484 if ( empty( $this->mLimitReportJSData ) ) {
1485 $this->mLimitReportJSData = $source->mLimitReportJSData;
1486 }
1487 }
1488
1497 // HTML and HTTP
1498 $this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
1499 $this->mModules = self::mergeList( $this->mModules, $source->getModules() );
1500 $this->mModuleStyles = self::mergeList( $this->mModuleStyles, $source->getModuleStyles() );
1501 $this->mJsConfigVars = self::mergeMap( $this->mJsConfigVars, $source->getJsConfigVars() );
1502 $this->mMaxAdaptiveExpiry = min( $this->mMaxAdaptiveExpiry, $source->mMaxAdaptiveExpiry );
1503 $this->mExtraStyleSrcs = self::mergeList(
1504 $this->mExtraStyleSrcs,
1505 $source->getExtraCSPStyleSrcs()
1506 );
1507 $this->mExtraScriptSrcs = self::mergeList(
1508 $this->mExtraScriptSrcs,
1509 $source->getExtraCSPScriptSrcs()
1510 );
1511 $this->mExtraDefaultSrcs = self::mergeList(
1512 $this->mExtraDefaultSrcs,
1513 $source->getExtraCSPDefaultSrcs()
1514 );
1515
1516 // "noindex" always wins!
1517 if ( $this->mIndexPolicy === 'noindex' || $source->mIndexPolicy === 'noindex' ) {
1518 $this->mIndexPolicy = 'noindex';
1519 } elseif ( $this->mIndexPolicy !== 'index' ) {
1520 $this->mIndexPolicy = $source->mIndexPolicy;
1521 }
1522
1523 // Skin control
1524 $this->mNewSection = $this->mNewSection || $source->getNewSection();
1525 $this->mHideNewSection = $this->mHideNewSection || $source->getHideNewSection();
1526 $this->mNoGallery = $this->mNoGallery || $source->getNoGallery();
1527 $this->mEnableOOUI = $this->mEnableOOUI || $source->getEnableOOUI();
1528 $this->mPreventClickjacking = $this->mPreventClickjacking || $source->preventClickjacking();
1529
1530 // TODO: we'll have to be smarter about this!
1531 $this->mSections = array_merge( $this->mSections, $source->getSections() );
1532 $this->mTOCHTML .= $source->mTOCHTML;
1533
1534 // XXX: we don't want to concatenate title text, so first write wins.
1535 // We should use the first *modified* title text, but we don't have the original to check.
1536 if ( $this->mTitleText === null || $this->mTitleText === '' ) {
1537 $this->mTitleText = $source->mTitleText;
1538 }
1539
1540 // class names are stored in array keys
1541 $this->mWrapperDivClasses = self::mergeMap(
1542 $this->mWrapperDivClasses,
1543 $source->mWrapperDivClasses
1544 );
1545
1546 // NOTE: last write wins, same as within one ParserOutput
1547 $this->mIndicators = self::mergeMap( $this->mIndicators, $source->getIndicators() );
1548
1549 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
1550 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
1551 // kinds of extension data to be merged in different ways.
1552 $this->mExtensionData = self::mergeMap(
1553 $this->mExtensionData,
1554 $source->mExtensionData
1555 );
1556 }
1557
1566 $this->mLanguageLinks = self::mergeList( $this->mLanguageLinks, $source->getLanguageLinks() );
1567 $this->mCategories = self::mergeMap( $this->mCategories, $source->getCategories() );
1568 $this->mLinks = self::merge2D( $this->mLinks, $source->getLinks() );
1569 $this->mTemplates = self::merge2D( $this->mTemplates, $source->getTemplates() );
1570 $this->mTemplateIds = self::merge2D( $this->mTemplateIds, $source->getTemplateIds() );
1571 $this->mImages = self::mergeMap( $this->mImages, $source->getImages() );
1572 $this->mFileSearchOptions = self::mergeMap(
1573 $this->mFileSearchOptions,
1574 $source->getFileSearchOptions()
1575 );
1576 $this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
1577 $this->mInterwikiLinks = self::merge2D(
1578 $this->mInterwikiLinks,
1579 $source->getInterwikiLinks()
1580 );
1581
1582 // TODO: add a $mergeStrategy parameter to setProperty to allow different
1583 // kinds of properties to be merged in different ways.
1584 $this->mProperties = self::mergeMap( $this->mProperties, $source->getProperties() );
1585
1586 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
1587 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
1588 // kinds of extension data to be merged in different ways.
1589 $this->mExtensionData = self::mergeMap(
1590 $this->mExtensionData,
1591 $source->mExtensionData
1592 );
1593 }
1594
1595 private static function mergeMixedList( array $a, array $b ) {
1596 return array_unique( array_merge( $a, $b ), SORT_REGULAR );
1597 }
1598
1599 private static function mergeList( array $a, array $b ) {
1600 return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
1601 }
1602
1603 private static function mergeMap( array $a, array $b ) {
1604 return array_replace( $a, $b );
1605 }
1606
1607 private static function merge2D( array $a, array $b ) {
1608 $values = [];
1609 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1610
1611 foreach ( $keys as $k ) {
1612 if ( empty( $a[$k] ) ) {
1613 $values[$k] = $b[$k];
1614 } elseif ( empty( $b[$k] ) ) {
1615 $values[$k] = $a[$k];
1616 } elseif ( is_array( $a[$k] ) && is_array( $b[$k] ) ) {
1617 $values[$k] = array_replace( $a[$k], $b[$k] );
1618 } else {
1619 $values[$k] = $b[$k];
1620 }
1621 }
1622
1623 return $values;
1624 }
1625
1626 private static function useEachMinValue( array $a, array $b ) {
1627 $values = [];
1628 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1629
1630 foreach ( $keys as $k ) {
1631 if ( is_array( $a[$k] ?? null ) && is_array( $b[$k] ?? null ) ) {
1632 $values[$k] = self::useEachMinValue( $a[$k], $b[$k] );
1633 } else {
1634 $values[$k] = self::useMinValue( $a[$k] ?? null, $b[$k] ?? null );
1635 }
1636 }
1637
1638 return $values;
1639 }
1640
1641 private static function useMinValue( $a, $b ) {
1642 if ( $a === null ) {
1643 return $b;
1644 }
1645
1646 if ( $b === null ) {
1647 return $a;
1648 }
1649
1650 return min( $a, $b );
1651 }
1652
1653 private static function useMaxValue( $a, $b ) {
1654 if ( $a === null ) {
1655 return $b;
1656 }
1657
1658 if ( $b === null ) {
1659 return $a;
1660 }
1661
1662 return max( $a, $b );
1663 }
1664
1665}
$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,...
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.
PSR-3 logger instance factory.
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)
array $mModuleStyles
Modules of which only the CSSS will be loaded by ResourceLoader.
unsetProperty( $name)
static getTimes( $clock=null)
hideNewSection( $value)
array $mFlags
Generic flags.
bool $mPreventClickjacking
Whether to emit X-Frame-Options: DENY.
array $mTemplateIds
2-D map of NS/DBK to rev ID for the template references.
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)
addExtraCSPScriptSrc( $src)
Add an extra value to Content-Security-Policy script-src directive.
array $mJsConfigVars
JavaScript config variable for mw.config combined with this page.
bool $mHideNewSection
Hide the new section link?
setCategoryLinks( $cl)
array $mTemplates
2-D map of NS/DBK to ID for the template references.
setRevisionUsedSha1Base36( $hash)
static merge2D(array $a, array $b)
setNoGallery( $value)
hasText()
Returns true if text was passed to the constructor, or set using setText().
string $mTOCHTML
HTML of the TOC.
static mergeList(array $a, array $b)
addInterwikiLink( $title)
setRevisionTimestampUsed( $timestamp)
array $mLimitReportData
Parser limit report data.
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.
int[][] $mLinks
2-D map of NS/DBK to ID for the links in the document.
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.
bool $mNewSection
Show a new section link?
array $mImages
DB keys of the images used, in the array key only.
string $mTimestamp
Timestamp of the revision.
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.
bool $mEnableOOUI
Whether OOUI should be enabled.
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.
getExtraCSPStyleSrcs()
Get extra Content-Security-Policy 'style-src' directives.
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.
array $mExternalLinks
External link URLs, in the key only.
getExtraCSPScriptSrcs()
Get extra Content-Security-Policy 'script-src' directives.
array $mInterwikiLinks
2-D map of prefix/DBK (in keys only) for the inline interwiki links in the document.
array $mIndicators
Page status indicators, usually displayed in top-right corner.
static useMinValue( $a, $b)
getExtensionData( $key)
Gets extensions data previously attached to this ParserOutput using setExtensionData().
array $mExtraScriptSrcs
Extra script-src for CSP.
clearWrapperDivClass()
Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div until addWrap...
array $mExtraDefaultSrcs
Extra default-src for CSP [Everything but script and style].
getText( $options=[])
Get the output HTML.
array $mExtensionData
extra data used by extensions.
true[] $mAccessedOptions
List of ParserOptions (stored in the keys).
int null $revisionTimestampUsed
Assumed rev timestamp for {{REVISIONTIMESTAMP}} if no revision is set.
getExtraCSPDefaultSrcs()
Get extra Content-Security-Policy 'default-src' directives.
string $mTitleText
Title text of the chosen language variant, as HTML.
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.
array $mOutputHooks
Hook tags as per $wgParserOutputHooks.
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...
array $mCategories
Map of category names to sort keys.
array $mSections
Table of contents.
array $mLinksSpecial
Keys are DBKs for the links to special pages in the document.
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
bool $mNoGallery
No gallery on category page? (NOGALLERY).
int null $speculativePageIdUsed
Assumed page ID for {{PAGEID}} if no revision is set.
setExtensionData( $key, $value)
Attaches arbitrary data to this ParserObject.
setTimestamp( $timestamp)
array $mHeadItems
Items to put in the <head> section.
array $mLanguageLinks
List of the full text of language links, in the order they appear.
setSpeculativeRevIdUsed( $id)
__construct( $text='', $languageLinks=[], $categoryLinks=[], $unused=false, $titletext='')
array $mModules
Modules to be loaded by ResourceLoader.
setSpeculativePageIdUsed( $id)
static isLinkInternal( $internal, $url)
Checks, if a url is pointing to the own server.
addExtraCSPDefaultSrc( $src)
Add an extra value to Content-Security-Policy default-src directive.
array $mProperties
Name/value pairs to be cached in the DB.
array $mWarnings
Warning text to be returned to the user.
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)
getTimeSinceStart( $clock)
Returns the time since resetParseStartTime() was last called.
addExtraCSPStyleSrc( $src)
Add an extra value to Content-Security-Policy style-src directive.
string $mIndexPolicy
'index' or 'noindex'? Any other value will result in no change.
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.
const EDITSECTION_REGEX
array $mFileSearchOptions
DB keys of the images used mapped to sha1 and MW timestamp.
addCategory( $c, $sort)
setIndexPolicy( $policy)
array $mParseStartTime
Timestamps for getTimeSinceStart().
string null $revisionUsedSha1Base36
SHA-1 base 36 hash of any self-transclusion.
array $mExtraStyleSrcs
Extra style-src for CSP.
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:76
const NS_SPECIAL
Definition Defines.php:59
const NS_MEDIA
Definition Defines.php:58
const NS_CATEGORY
Definition Defines.php:84
$source
$content
Definition router.php:76