MediaWiki master
ParserOutput.php
Go to the documentation of this file.
1<?php
2declare( strict_types = 1 );
3
9namespace MediaWiki\Parser;
10
11use DateTimeImmutable;
12use DateTimeZone;
13use InvalidArgumentException;
14use LogicException;
25use UnhandledMatchError;
26use Wikimedia\Assert\Assert;
27use Wikimedia\Bcp47Code\Bcp47Code;
28use Wikimedia\Bcp47Code\Bcp47CodeValue;
29use Wikimedia\JsonCodec\Hint;
32use Wikimedia\Parsoid\Core\ContentMetadataCollector;
33use Wikimedia\Parsoid\Core\ContentMetadataCollectorCompat;
34use Wikimedia\Parsoid\Core\HtmlPageBundle;
35use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
36use Wikimedia\Parsoid\Core\MergeStrategy;
37use Wikimedia\Parsoid\Core\TOCData;
38use Wikimedia\Parsoid\DOM\DocumentFragment;
39
90class ParserOutput extends CacheTime implements ContentMetadataCollector {
91 // This is used to break cyclic dependencies and allow a measure
92 // of compatibility when new methods are added to ContentMetadataCollector
93 // by Parsoid.
94 use ContentMetadataCollectorCompat;
95
100 public const PARSOID_PAGE_BUNDLE_KEY = 'parsoid-page-bundle';
101
106 public const MW_MERGE_STRATEGY_KEY = '_mw-strategy';
107
119 public const MW_MERGE_STRATEGY_UNION = MergeStrategy::UNION;
120
121 private ContentHolder $contentHolder;
122
126 private array $mLanguageLinkMap = [];
127
131 private array $mCategories = [];
132
138 private array $mIndicatorIds = [];
139
143 private string $mTitleText;
144
149 private array $mLinks = [];
150
155 private array $mLinksSpecial = [];
156
161 private array $mTemplates = [];
162
167 private array $mTemplateIds = [];
168
172 private array $mImages = [];
173
177 private array $mFileSearchOptions = [];
178
182 private array $mExternalLinks = [];
183
188 private array $mInterwikiLinks = [];
189
193 private array $existenceLinks = [];
194
198 private array $mHeadItems = [];
199
203 private array $mModuleSet = [];
204
208 private array $mModuleStyleSet = [];
209
213 private array $mJsConfigVars = [];
214
220 private array $mWarnings = [];
221
226 private array $mWarningMsgs = [];
227
231 private ?TOCData $mTOCData = null;
232
236 private array $mProperties = [];
237
241 private ?string $mTimestamp = null;
242
246 private array $mExtensionData = [];
247
251 private array $mLimitReportData = [];
252
254 private array $mLimitReportJSData = [];
255
257 private string $mCacheMessage = '';
258
262 private array $mParseStartTime = [];
263
267 private array $mTimeProfile = [];
268
272 private array $mExtraScriptSrcs = [];
273
277 private array $mExtraDefaultSrcs = [];
278
282 private array $mExtraStyleSrcs = [];
283
287 private $mFlags = [];
288
289 private const SPECULATIVE_FIELDS = [
290 'speculativePageIdUsed',
291 'mSpeculativeRevId',
292 'revisionTimestampUsed',
293 ];
294
296 private ?int $mSpeculativeRevId = null;
298 private ?int $speculativePageIdUsed = null;
300 private ?string $revisionTimestampUsed = null;
301
303 private ?string $revisionUsedSha1Base36 = null;
304
309 private array $mWrapperDivClasses = [];
310
315 private ?int $mMaxAdaptiveExpiry = null;
316
317 // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
318 // Current values imply that m=3933.333333 and b=-333.333333
319 // See https://www.nngroup.com/articles/website-response-times/
320 private const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
321 private const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
322 private const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
323 private const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
324 private const MIN_AR_TTL = 15; // min adaptive TTL (for pool counter, and edit stashing)
325
336 public function __construct( ?string $text = null, array $languageLinks = [], array $categoryLinks = [],
337 $unused = false, string $titletext = ''
338 ) {
339 if ( $text === null ) {
340 $this->contentHolder = ContentHolder::createEmpty();
341 } else {
342 $this->contentHolder = ContentHolder::createFromLegacyString( $text );
343 }
344 $this->mCategories = $categoryLinks;
345 $this->mTitleText = $titletext;
346 foreach ( $languageLinks as $ll ) {
347 $this->addLanguageLink( $ll );
348 }
349 // If the content handler does not specify an alternative (by
350 // calling ::resetParseStartTime() at a later point) then use
351 // the creation of the ParserOutput as the "start of parse" time.
352 $this->resetParseStartTime();
353 }
354
361 public function getContentHolder(): ContentHolder {
362 return $this->contentHolder;
363 }
364
369 public function setContentHolder( ContentHolder $contentHolder ) {
370 $this->contentHolder = $contentHolder;
371 }
372
383 public function hasText(): bool {
384 return $this->contentHolder->has( ContentHolder::BODY_FRAGMENT );
385 }
386
396 public function getRawText() {
397 wfDeprecated( __METHOD__, '1.45' );
398 return $this->getContentHolderText();
399 }
400
401 /*
402 * @unstable This method is transitional and will be replaced by a method
403 * in another class, maybe ContentRenderer. It allows us to break our
404 * porting work into two steps; in the first we bring ParserOptions to
405 * to each callsite to ensure it is made available to the
406 * postprocessing pipeline. In the second we move this functionality
407 * into the Content hierarchy and out of ParserOutput, which should become
408 * a pure value object.
409 *
410 * @param ParserOptions $popts
411 * @param array $options (since 1.31) Transformations to apply to the HTML
412 * - allowClone: (bool) Whether to clone the ParserOutput before
413 * applying transformations. Default is true.
414 * - allowTOC: (bool) Show the TOC, assuming there were enough headings
415 * to generate one and `__NOTOC__` wasn't used. Default is true,
416 * but might be statefully overridden.
417 * - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
418 * otherwise the marker will be left in the article (and the skin
419 * will be responsible for replacing or removing it). Default is
420 * true.
421 * - enableSectionEditLinks: (bool) Include section edit links, assuming
422 * section edit link tokens are present in the HTML. Default is true,
423 * but might be statefully overridden.
424 * - userLang: (Language) Language object used for localizing UX messages,
425 * for example the heading of the table of contents. If omitted, will
426 * use the language of the main request context.
427 * - skin: (Skin) Skin object used for transforming section edit links.
428 * - unwrap: (bool) Return text without a wrapper div. Default is false,
429 * meaning a wrapper div will be added if getWrapperDivClass() returns
430 * a non-empty string.
431 * - wrapperDivClass: (string) Wrap the output in a div and apply the given
432 * CSS class to that div. This overrides the output of getWrapperDivClass().
433 * Setting this to an empty string has the same effect as 'unwrap' => true.
434 * - deduplicateStyles: (bool) When true, which is the default, `<style>`
435 * tags with the `data-mw-deduplicate` attribute set are deduplicated by
436 * value of the attribute: all but the first will be replaced by `<link
437 * rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
438 * the scheme-specific-part of the href is the (percent-encoded) value
439 * of the `data-mw-deduplicate` attribute.
440 * - absoluteURLs: (bool) use absolute URLs in all links. Default: false
441 * - includeDebugInfo: (bool) render PP limit report in HTML. Default: false
442 * It is planned to eventually deprecate this $options array and to be able to
443 * pass its content in the $popts ParserOptions.
444 * @return ParserOutput
445 */
446 public function runOutputPipeline( ParserOptions $popts, array $options = [] ): ParserOutput {
447 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
448 $options += [
449 'allowClone' => true,
450 'allowTOC' => true,
451 'injectTOC' => true,
452 'enableSectionEditLinks' => true,
453 'userLang' => null,
454 'skin' => null,
455 'unwrap' => false,
456 'wrapperDivClass' => $this->getWrapperDivClass(),
457 'deduplicateStyles' => true,
458 'absoluteURLs' => false,
459 'includeDebugInfo' => false,
460 ];
461 return $pipeline->run( $this, $popts, $options );
462 }
463
469 public function addCacheMessage( string $msg ): void {
470 $this->mCacheMessage .= $msg;
471 }
472
478 public function addWrapperDivClass( $class ): void {
479 $this->mWrapperDivClasses[$class] = true;
480 }
481
486 public function clearWrapperDivClass(): void {
487 $this->mWrapperDivClasses = [];
488 }
489
495 public function getWrapperDivClass(): string {
496 return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
497 }
498
503 public function setSpeculativeRevIdUsed( $id ): void {
504 $this->mSpeculativeRevId = $id;
505 }
506
511 public function getSpeculativeRevIdUsed(): ?int {
512 return $this->mSpeculativeRevId;
513 }
514
519 public function setSpeculativePageIdUsed( $id ): void {
520 $this->speculativePageIdUsed = $id;
521 }
522
527 public function getSpeculativePageIdUsed() {
528 return $this->speculativePageIdUsed;
529 }
530
535 public function setRevisionTimestampUsed( $timestamp ): void {
536 $this->revisionTimestampUsed = $timestamp;
537 }
538
543 public function getRevisionTimestampUsed() {
544 return $this->revisionTimestampUsed;
545 }
546
551 public function setRevisionUsedSha1Base36( $hash ): void {
552 if ( $hash === null ) {
553 return; // e.g. RevisionRecord::getSha1() returned null
554 }
555
556 if (
557 $this->revisionUsedSha1Base36 !== null &&
558 $this->revisionUsedSha1Base36 !== $hash
559 ) {
560 $this->revisionUsedSha1Base36 = ''; // mismatched
561 } else {
562 $this->revisionUsedSha1Base36 = $hash;
563 }
564 }
565
570 public function getRevisionUsedSha1Base36() {
571 return $this->revisionUsedSha1Base36;
572 }
573
579 public function getLanguageLinks() {
580 wfDeprecated( __METHOD__, '1.43' );
581 return $this->getLanguageLinksInternal();
582 }
583
587 private function getLanguageLinksInternal(): array {
588 $result = [];
589 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
590 $result[] = "$lang:$title";
591 }
592 return $result;
593 }
594
596 public function getInterwikiLinks() {
597 wfDeprecated( __METHOD__, '1.43' );
598 return $this->mInterwikiLinks;
599 }
600
608 public function getCategoryNames(): array {
609 # Note that numeric category names get converted to 'int' when
610 # stored as array keys; stringify the keys to ensure they
611 # return to original string form so as not to confuse callers.
612 return array_map( 'strval', array_keys( $this->mCategories ) );
613 }
614
626 public function getCategoryMap(): array {
627 return $this->mCategories;
628 }
629
643 public function getCategorySortKey( string $name ): ?string {
644 // This API avoids exposing the fact that numeric string category
645 // names are going to be converted to 'int' when used as array
646 // keys for the `mCategories` field.
647 return $this->mCategories[$name] ?? null;
648 }
649
654 public function getIndicators(): array {
655 $result = [];
656 foreach ( $this->mIndicatorIds as $id ) {
657 $fragmentName = "indicator:{$id}";
658 $contents = $this->contentHolder->getAsHtmlString( $fragmentName );
659 Assert::invariant( $contents !== null, "fragments should exist" );
660 $result[$id] = $contents;
661 }
662 return $result;
663 }
664
665 public function getTitleText(): string {
666 return $this->mTitleText;
667 }
668
673 public function getTOCData(): ?TOCData {
674 return $this->mTOCData;
675 }
676
681 public function getCacheMessage(): string {
682 return $this->mCacheMessage;
683 }
684
689 public function getSections(): array {
690 if ( $this->mTOCData !== null ) {
691 return $this->mTOCData->toLegacy();
692 }
693 // For compatibility
694 return [];
695 }
696
715 public function getLinkList( string|ParserOutputLinkTypes $linkType, ?int $onlyNamespace = null ): array {
716 if ( is_string( $linkType ) ) {
717 $linkType = ParserOutputLinkTypes::from( $linkType );
718 }
719 # Note that fragments are dropped for everything except language links
720 $result = [];
721 switch ( $linkType ) {
722 case ParserOutputLinkTypes::CATEGORY:
723 if ( $onlyNamespace !== null && $onlyNamespace !== NS_CATEGORY ) {
724 return [];
725 }
726 foreach ( $this->mCategories as $dbkey => $sort ) {
727 $result[] = [
728 'link' => new TitleValue( NS_CATEGORY, (string)$dbkey ),
729 'sort' => $sort,
730 ];
731 }
732 break;
733
734 case ParserOutputLinkTypes::EXISTENCE:
735 $links = $onlyNamespace === null ? $this->existenceLinks : [
736 $onlyNamespace => $this->existenceLinks[$onlyNamespace] ?? [],
737 ];
738 foreach ( $links as $ns => $titles ) {
739 foreach ( $titles as $dbkey => $unused ) {
740 $result[] = [
741 'link' => new TitleValue( $ns, (string)$dbkey )
742 ];
743 }
744 }
745 break;
746
747 case ParserOutputLinkTypes::INTERWIKI:
748 // By convention interwiki links belong to NS_MAIN
749 if ( $onlyNamespace !== null && $onlyNamespace !== NS_MAIN ) {
750 return [];
751 }
752 foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
753 foreach ( $arr as $dbkey => $ignore ) {
754 $result[] = [
755 'link' => new TitleValue( NS_MAIN, (string)$dbkey, '', (string)$prefix ),
756 ];
757 }
758 }
759 break;
760
761 case ParserOutputLinkTypes::LANGUAGE:
762 // By convention language links belong to NS_MAIN
763 if ( $onlyNamespace !== null && $onlyNamespace !== NS_MAIN ) {
764 return [];
765 }
766 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
767 # language links can have fragments!
768 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
769 $result[] = [
770 'link' => new TitleValue( NS_MAIN, $title, $frag, (string)$lang ),
771 ];
772 }
773 break;
774
775 case ParserOutputLinkTypes::LOCAL:
776 $links = $onlyNamespace === null ? $this->mLinks : [
777 $onlyNamespace => $this->mLinks[$onlyNamespace] ?? [],
778 ];
779 foreach ( $links as $ns => $arr ) {
780 foreach ( $arr as $dbkey => $id ) {
781 $result[] = [
782 'link' => new TitleValue( $ns, (string)$dbkey ),
783 'pageid' => $id,
784 ];
785 }
786 }
787 break;
788
789 case ParserOutputLinkTypes::MEDIA:
790 if ( $onlyNamespace !== null && $onlyNamespace !== NS_FILE ) {
791 return [];
792 }
793 foreach ( $this->mImages as $dbkey => $ignore ) {
794 $extra = $this->mFileSearchOptions[$dbkey] ?? [];
795 $extra['link'] = new TitleValue( NS_FILE, (string)$dbkey );
796 $result[] = $extra;
797 }
798 break;
799
800 case ParserOutputLinkTypes::SPECIAL:
801 if ( $onlyNamespace !== null && $onlyNamespace !== NS_SPECIAL ) {
802 return [];
803 }
804 foreach ( $this->mLinksSpecial as $dbkey => $ignore ) {
805 $result[] = [
806 'link' => new TitleValue( NS_SPECIAL, (string)$dbkey ),
807 ];
808 }
809 break;
810
811 case ParserOutputLinkTypes::TEMPLATE:
812 $links = $onlyNamespace === null ? $this->mTemplates : [
813 $onlyNamespace => $this->mTemplates[$onlyNamespace] ?? [],
814 ];
815 foreach ( $links as $ns => $arr ) {
816 foreach ( $arr as $dbkey => $pageid ) {
817 $result[] = [
818 'link' => new TitleValue( $ns, (string)$dbkey ),
819 'pageid' => $pageid,
820 // default to invalid/broken revision if this is not present
821 'revid' => $this->mTemplateIds[$ns][$dbkey] ?? 0,
822 ];
823 }
824 }
825 break;
826
827 default:
828 throw new UnhandledMatchError( "Unknown link type " . $linkType->value );
829 }
830 return $result;
831 }
832
846 public function appendLinkList( string|ParserOutputLinkTypes $linkType, array $linkItem ): void {
847 if ( is_string( $linkType ) ) {
848 $linkType = ParserOutputLinkTypes::from( $linkType );
849 }
850 $link = $linkItem['link'];
851 match ( $linkType ) {
852 ParserOutputLinkTypes::CATEGORY =>
853 $this->addCategory( $link, $linkItem['sort'] ?? '' ),
854 ParserOutputLinkTypes::EXISTENCE =>
855 $this->addExistenceDependency( $link ),
856 ParserOutputLinkTypes::INTERWIKI =>
857 $this->addInterwikiLink( $link ),
858 ParserOutputLinkTypes::LANGUAGE =>
859 $this->addLanguageLink( $link ),
860 ParserOutputLinkTypes::LOCAL =>
861 $this->addLink( $link, $linkItem['pageid'] ?? null ),
862 ParserOutputLinkTypes::MEDIA =>
863 $this->addImage( $link, $linkItem['time'] ?? null, $linkItem['sha1'] ?? null ),
864 ParserOutputLinkTypes::SPECIAL =>
865 $this->addLink( $link ),
866 ParserOutputLinkTypes::TEMPLATE =>
867 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
868 $this->addTemplate( $link, $linkItem['pageid'], $linkItem['revid'] ),
869 };
870 }
871
873 public function &getLinks() {
874 wfDeprecated( __METHOD__, '1.43' );
875 return $this->mLinks;
876 }
877
884 public function hasLinks(): bool {
885 foreach ( $this->mLinks as $ns => $arr ) {
886 foreach ( $arr as $dbkey => $id ) {
887 return true;
888 }
889 }
890 return false;
891 }
892
898 public function &getLinksSpecial() {
899 wfDeprecated( __METHOD__, '1.43' );
900 return $this->mLinksSpecial;
901 }
902
904 public function &getTemplates() {
905 wfDeprecated( __METHOD__, '1.43' );
906 return $this->mTemplates;
907 }
908
910 public function &getTemplateIds() {
911 wfDeprecated( __METHOD__, '1.43' );
912 return $this->mTemplateIds;
913 }
914
916 public function &getImages() {
917 wfDeprecated( __METHOD__, '1.43' );
918 return $this->mImages;
919 }
920
926 public function hasImages(): bool {
927 return $this->mImages !== [];
928 }
929
931 public function &getFileSearchOptions() {
932 wfDeprecated( __METHOD__, '1.43' );
933 return $this->mFileSearchOptions;
934 }
935
942 public function &getExternalLinks(): array {
943 return $this->mExternalLinks;
944 }
945
950 public function setNoGallery( $value ): void {
951 $this->setOutputFlag( ParserOutputFlags::NO_GALLERY, (bool)$value );
952 }
953
958 public function getNoGallery() {
959 return $this->getOutputFlag( ParserOutputFlags::NO_GALLERY );
960 }
961
965 public function getHeadItems() {
966 return $this->mHeadItems;
967 }
968
972 public function getModules() {
973 return array_keys( $this->mModuleSet );
974 }
975
979 public function getModuleStyles() {
980 return array_keys( $this->mModuleStyleSet );
981 }
982
990 public function getJsConfigVars( bool $showStrategyKeys = false ) {
991 $result = $this->mJsConfigVars;
992 // Don't expose the internal strategy key
993 foreach ( $result as &$value ) {
994 if ( is_array( $value ) && !$showStrategyKeys ) {
995 if ( ( $value[self::MW_MERGE_STRATEGY_KEY] ?? null ) === MergeStrategy::SUM->value ) {
996 $value = $value['value'];
997 continue;
998 }
999 unset( $value[self::MW_MERGE_STRATEGY_KEY] );
1000 }
1001 }
1002 return $result;
1003 }
1004
1006 public function getWarnings(): array {
1007 // T343048: Don't emit deprecation warnings here until the
1008 // compatibility fallback in ApiParse is removed.
1009 return array_keys( $this->mWarnings );
1010 }
1011
1013 public function getWarningMsgs(): array {
1014 return array_values( $this->mWarningMsgs );
1015 }
1016
1017 public function getIndexPolicy(): string {
1018 // 'noindex' wins if both are set. (T16899)
1019 if ( $this->getOutputFlag( ParserOutputFlags::NO_INDEX_POLICY ) ) {
1020 return 'noindex';
1021 } elseif ( $this->getOutputFlag( ParserOutputFlags::INDEX_POLICY ) ) {
1022 return 'index';
1023 }
1024 return '';
1025 }
1026
1030 public function getRevisionTimestamp(): ?string {
1031 return $this->mTimestamp;
1032 }
1033
1038 public function getTimestamp() {
1039 wfDeprecated( __METHOD__, '1.42' );
1040 return $this->getRevisionTimestamp();
1041 }
1042
1046 public function getLimitReportData() {
1047 return $this->mLimitReportData;
1048 }
1049
1053 public function getLimitReportJSData() {
1054 return $this->mLimitReportJSData;
1055 }
1056
1061 public function getEnableOOUI() {
1062 return $this->getOutputFlag( ParserOutputFlags::ENABLE_OOUI );
1063 }
1064
1070 public function getExtraCSPDefaultSrcs() {
1071 return $this->mExtraDefaultSrcs;
1072 }
1073
1079 public function getExtraCSPScriptSrcs() {
1080 return $this->mExtraScriptSrcs;
1081 }
1082
1088 public function getExtraCSPStyleSrcs() {
1089 return $this->mExtraStyleSrcs;
1090 }
1091
1102 public function setRawText( ?string $text ): void {
1103 wfDeprecated( __METHOD__, '1.45' );
1104 $this->setContentHolderText( $text );
1105 }
1106
1119 public function setText( $text ) {
1120 wfDeprecated( __METHOD__, '1.42' );
1121 $ret = $this->hasText() ? $this->getContentHolderText() : null;
1122 $this->setContentHolderText( $text );
1123 return $ret;
1124 }
1125
1129 public function setLanguageLinks( $ll ) {
1130 wfDeprecated( __METHOD__, '1.42' );
1131 $old = $this->getLanguageLinksInternal();
1132 $this->mLanguageLinkMap = [];
1133 if ( $ll === null ) { // T376323
1134 wfDeprecated( __METHOD__ . ' with null argument', '1.43' );
1135 }
1136 foreach ( ( $ll ?? [] ) as $l ) {
1137 $this->addLanguageLink( $l );
1138 }
1139 return $old;
1140 }
1141
1143 public function clearLanguageLinks(): void {
1144 $this->mLanguageLinkMap = [];
1145 }
1146
1151 public function setTitleText( string $t ) {
1152 return wfSetVar( $this->mTitleText, $t );
1153 }
1154
1158 public function setTOCData( TOCData $tocData ): void {
1159 $this->mTOCData = $tocData;
1160 }
1161
1166 public function setSections( array $sectionArray ) {
1167 $oldValue = $this->getSections();
1168 $this->setTOCData( TOCData::fromLegacy( $sectionArray ) );
1169 return $oldValue;
1170 }
1171
1185 public function setIndexPolicy( $policy ): string {
1186 $old = $this->getIndexPolicy();
1187 if ( $policy === 'noindex' ) {
1188 $this->setOutputFlag( ParserOutputFlags::NO_INDEX_POLICY );
1189 } elseif ( $policy === 'index' ) {
1190 $this->setOutputFlag( ParserOutputFlags::INDEX_POLICY );
1191 }
1192 return $old;
1193 }
1194
1198 public function setRevisionTimestamp( ?string $timestamp ): void {
1199 $this->mTimestamp = $timestamp;
1200 }
1201
1208 public function setTimestamp( $timestamp ) {
1209 wfDeprecated( __METHOD__, '1.42' );
1210 return wfSetVar( $this->mTimestamp, $timestamp );
1211 }
1212
1228 public function addCategory( $c, $sort = '' ): void {
1229 if ( $c instanceof ParsoidLinkTarget ) {
1230 $c = $c->getDBkey();
1231 }
1232 if ( ( $this->mCategories[$c] ?? $sort ) !== $sort ) {
1233 // Overwriting a category sort key prevents selective update
1234 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1235 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1236 }
1237 $this->mCategories[$c] = $sort;
1238 }
1239
1245 public function setCategories( array $c ): void {
1246 if ( ( $this->mCategories ?: $c ) !== $c ) {
1247 // Overwriting categories prevents selective update
1248 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1249 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1250 }
1251 $this->mCategories = $c;
1252 }
1253
1260 public function setIndicator( $id, $content ): void {
1261 Assert::parameterType( 'string', $content, 'content' );
1262 $fragmentName = "indicator:{$id}";
1263 if ( $this->contentHolder->has( $fragmentName ) ) {
1264 // Overwriting an indicator prevents selective update
1265 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1266 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1267 } else {
1268 $this->mIndicatorIds[] = $id;
1269 }
1270 $this->getContentHolder()->setAsHtmlString( $fragmentName, $content );
1271 }
1272
1276 public function setIndicatorDom( string $id, DocumentFragment $content ): void {
1277 $fragmentName = "indicator:{$id}";
1278 if ( $this->contentHolder->has( $fragmentName ) ) {
1279 // Overwriting an indicator prevents selective update
1280 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1281 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1282 } else {
1283 $this->mIndicatorIds[] = $id;
1284 }
1285 $this->getContentHolder()->setAsDom( $fragmentName, $content );
1286 }
1287
1296 public function setEnableOOUI( bool $enable = false ): void {
1297 $this->setOutputFlag( ParserOutputFlags::ENABLE_OOUI, $enable );
1298 }
1299
1304 public function addLanguageLink( $t ): void {
1305 # Note that fragments are preserved
1306 if ( $t instanceof ParsoidLinkTarget ) {
1307 // Language links are unusual in using 'text' rather than 'db key'
1308 // Note that fragments are preserved.
1309 $lang = $t->getInterwiki();
1310 $title = $t->getText();
1311 if ( $t->hasFragment() ) {
1312 $title .= '#' . $t->getFragment();
1313 }
1314 } else {
1315 [ $lang, $title ] = array_pad( explode( ':', $t, 2 ), -2, '' );
1316 }
1317 if ( $lang === '' ) {
1318 throw new InvalidArgumentException( __METHOD__ . ' without prefix' );
1319 }
1320 if ( ( $this->mLanguageLinkMap[$lang] ?? $title ) !== $title ) {
1321 // Overwriting a language link prevents selective update
1322 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1323 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1324 }
1325 $this->mLanguageLinkMap[$lang] ??= $title;
1326 }
1327
1336 public function addWarningMsgVal( MessageSpecifier $mv, ?string $key = null ) {
1337 $mv = MessageValue::newFromSpecifier( $mv );
1338 $key ??= $mv->getKey();
1339 if ( array_key_exists( $key, $this->mWarningMsgs ) ) {
1340 // Overwriting a warning message prevents selective update
1341 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1342 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1343 }
1344 $this->mWarningMsgs[$key] = $mv;
1345 // Ensure callers aren't passing nonserializable arguments: T343048.
1346 $jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
1347 $path = $jsonCodec->detectNonSerializableData( $mv, true );
1348 if ( $path !== null ) {
1349 throw new InvalidArgumentException( __METHOD__ . ": nonserializable" );
1350 }
1351 // For backward compatibility with callers of ::getWarnings()
1352 // and rollback compatibility for ParserCache; don't remove
1353 // until we no longer need rollback compatibility with MW 1.43.
1354 $s = Message::newFromSpecifier( $mv )
1355 // some callers set the title here?
1356 ->inContentLanguage() // because this ends up in cache
1357 ->text();
1358 $this->mWarnings[$s] = 1;
1359 }
1360
1369 public function addWarningMsg( string $msg, ...$args ): void {
1370 // T227447: Once MessageSpecifier is moved to a library, Parsoid would
1371 // be able to use ::addWarningMsgVal() directly and this method
1372 // could be deprecated and removed.
1373 $this->addWarningMsgVal( MessageValue::new( $msg, $args ) );
1374 }
1375
1380 public function setNewSection( $value ): void {
1381 $this->setOutputFlag( ParserOutputFlags::NEW_SECTION, (bool)$value );
1382 }
1383
1388 public function setHideNewSection( bool $value ): void {
1389 $this->setOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION, $value );
1390 }
1391
1395 public function getHideNewSection(): bool {
1396 return $this->getOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION );
1397 }
1398
1402 public function getNewSection(): bool {
1403 return $this->getOutputFlag( ParserOutputFlags::NEW_SECTION );
1404 }
1405
1414 public static function isLinkInternal( $internal, $url ): bool {
1415 return (bool)preg_match( '/^' .
1416 # If server is proto relative, check also for http/https links
1417 ( str_starts_with( $internal, '//' ) ? '(?:https?:)?' : '' ) .
1418 preg_quote( $internal, '/' ) .
1419 # check for query/path/anchor or end of link in each case
1420 '(?:[\?\/\#]|$)/i',
1421 $url
1422 );
1423 }
1424
1428 public function addExternalLink( $url ): void {
1429 # We don't register links pointing to our own server, unless... :-)
1430 $config = MediaWikiServices::getInstance()->getMainConfig();
1431 $server = $config->get( MainConfigNames::Server );
1432 $registerInternalExternals = $config->get( MainConfigNames::RegisterInternalExternals );
1433 $ignoreDomains = $config->get( MainConfigNames::ExternalLinksIgnoreDomains );
1434
1435 # Replace unnecessary URL escape codes with the referenced character
1436 # This prevents spammers from hiding links from the filters
1437 $url = Parser::normalizeLinkUrl( $url );
1438
1439 $registerExternalLink = true;
1440 if ( !$registerInternalExternals ) {
1441 $registerExternalLink = !self::isLinkInternal( $server, $url );
1442 }
1443 if (
1444 MediaWikiServices::getInstance()->getUrlUtils()->matchesDomainList( $url, $ignoreDomains )
1445 ) {
1446 $registerExternalLink = false;
1447 }
1448 if ( $registerExternalLink ) {
1449 $this->mExternalLinks[$url] = 1;
1450 }
1451 }
1452
1459 public function addLink( ParsoidLinkTarget $link, $id = null ): void {
1460 if ( $link->isExternal() ) {
1461 // Don't record interwikis in pagelinks
1462 $this->addInterwikiLink( $link );
1463 return;
1464 }
1465 $ns = $link->getNamespace();
1466 $dbk = $link->getDBkey();
1467 if ( $ns === NS_MEDIA ) {
1468 // Normalize this pseudo-alias if it makes it down here...
1469 $ns = NS_FILE;
1470 } elseif ( $ns === NS_SPECIAL ) {
1471 // We don't want to record Special: links in the database, so put them in a separate place.
1472 // It might actually be wise to, but we'd need to do some normalization.
1473 $this->mLinksSpecial[$dbk] = 1;
1474 return;
1475 } elseif ( $dbk === '' ) {
1476 // Don't record self links - [[#Foo]]
1477 return;
1478 }
1479 if ( $id === null ) {
1480 // T357048: This actually kills performance; we should batch these.
1481 $page = MediaWikiServices::getInstance()->getPageStore()->getPageForLink( $link );
1482 $id = $page->getId();
1483 }
1484 $this->mLinks[$ns][$dbk] = $id;
1485 }
1486
1493 public function addImage( $name, $timestamp = null, $sha1 = null ): void {
1494 if ( $name instanceof ParsoidLinkTarget ) {
1495 $name = $name->getDBkey();
1496 }
1497 $this->mImages[$name] = 1;
1498 if ( $timestamp !== null && $sha1 !== null ) {
1499 $this->mFileSearchOptions[$name] = [ 'time' => $timestamp, 'sha1' => $sha1 ];
1500 }
1501 }
1502
1510 public function addTemplate( $link, $page_id, $rev_id ): void {
1511 if ( $link->isExternal() ) {
1512 // Will throw an InvalidArgumentException in a future release.
1513 throw new InvalidArgumentException( __METHOD__ . " with interwiki link" );
1514 }
1515 $ns = $link->getNamespace();
1516 $dbk = $link->getDBkey();
1517 // T357048: Parsoid doesn't have page_id
1518 $this->mTemplates[$ns][$dbk] = $page_id;
1519 $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
1520 }
1521
1526 public function addInterwikiLink( $link ): void {
1527 if ( !$link->isExternal() ) {
1528 throw new InvalidArgumentException( 'Non-interwiki link passed, internal parser error.' );
1529 }
1530 $prefix = $link->getInterwiki();
1531 $this->mInterwikiLinks[$prefix][$link->getDBkey()] = 1;
1532 }
1533
1541 public function addExistenceDependency( ParsoidLinkTarget $link ) {
1542 $ns = $link->getNamespace();
1543 $dbk = $link->getDBkey();
1544 // Ignore some kinds of links, as in addLink()
1545 if ( $link->isExternal() || $ns === NS_SPECIAL || $dbk === '' ) {
1546 return;
1547 }
1548 if ( $ns === NS_MEDIA ) {
1549 $ns = NS_FILE;
1550 }
1551 $this->existenceLinks[$ns][$dbk] = true;
1552 }
1553
1561 public function addHeadItem( $section, $tag = false ): void {
1562 if ( $tag !== false ) {
1563 $this->mHeadItems[$tag] = $section;
1564 } else {
1565 $this->mHeadItems[] = $section;
1566 }
1567 }
1568
1573 public function addModules( array $modules ): void {
1574 $modules = array_fill_keys( $modules, true );
1575 $this->mModuleSet = array_merge( $this->mModuleSet, $modules );
1576 }
1577
1582 public function addModuleStyles( array $modules ): void {
1583 $modules = array_fill_keys( $modules, true );
1584 $this->mModuleStyleSet = array_merge( $this->mModuleStyleSet, $modules );
1585 }
1586
1597 public function addJsConfigVars( $keys, $value = null ): void {
1598 wfDeprecated( __METHOD__, '1.38' );
1599 if ( is_array( $keys ) ) {
1600 foreach ( $keys as $key => $value ) {
1601 $this->mJsConfigVars[$key] = $value;
1602 }
1603 return;
1604 }
1605
1606 $this->mJsConfigVars[$keys] = $value;
1607 }
1608
1622 public function setJsConfigVar( string $key, $value ): void {
1623 if (
1624 array_key_exists( $key, $this->mJsConfigVars ) &&
1625 $this->mJsConfigVars[$key] !== $value
1626 ) {
1627 // Ensure that a key is mapped to only a single value in order
1628 // to prevent the resulting array from varying if content
1629 // is parsed in a different order.
1630 throw new InvalidArgumentException( "Multiple conflicting values given for $key" );
1631 }
1632 $this->mJsConfigVars[$key] = $value;
1633 }
1634
1651 public function appendJsConfigVar(
1652 string $key,
1653 $value,
1654 MergeStrategy|string $strategy = MergeStrategy::UNION
1655 ): void {
1656 if ( is_string( $strategy ) ) {
1657 $strategy = MergeStrategy::from( $strategy );
1658 }
1659 $this->mJsConfigVars = self::mergeMapStrategy(
1660 $this->mJsConfigVars,
1661 [ $key => self::makeMapStrategy( $value, $strategy ) ]
1662 );
1663 }
1664
1680 public function addOutputPageMetadata( OutputPage $out ): void {
1681 // This should eventually use the same merge mechanism used
1682 // internally to merge ParserOutputs together.
1683 // (ie: $this->mergeHtmlMetaDataFrom( $out->getMetadata() )
1684 // once preventClickjacking, moduleStyles, modules, jsconfigvars,
1685 // and head items are moved to OutputPage::$metadata)
1686
1687 // Take the strictest click-jacking policy. This is to ensure any one-click features
1688 // such as patrol or rollback on the transcluded special page will result in the wiki page
1689 // disallowing embedding in cross-origin iframes. Articles are generally allowed to be
1690 // embedded. Pages that transclude special pages are expected to be user pages or
1691 // other non-content pages that content re-users won't discover or care about.
1692 $this->setOutputFlag(
1693 ParserOutputFlags::PREVENT_CLICKJACKING,
1694 $this->getOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING ) ||
1695 $out->getOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING )
1696 );
1697
1698 $this->addModuleStyles( $out->getModuleStyles() );
1699
1700 // TODO: Figure out if style modules suffice, or whether the below is needed as well.
1701 // Are there special pages that permit transcluding/including and also have JS modules
1702 // that should be activate on the host page?
1703 $this->addModules( $out->getModules() );
1704 $this->mJsConfigVars = self::mergeMapStrategy(
1705 $this->mJsConfigVars, $out->getJsConfigVars()
1706 );
1707 $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
1708 }
1709
1721 public function setDisplayTitle( string $text ): void {
1722 $this->setTitleText( $text );
1723 $this->setPageProperty( 'displaytitle', $text );
1724 }
1725
1734 public function getDisplayTitle(): string|false {
1735 $t = $this->getTitleText();
1736 if ( $t === '' ) {
1737 return false;
1738 }
1739 return $t;
1740 }
1741
1750 public function getTitle(): ?ParsoidLinkTarget {
1751 $ns = $this->getExtensionData( 'core:title-ns' );
1752 $dbkey = $this->getExtensionData( 'core:title-dbkey' );
1753 if ( $dbkey !== null ) {
1754 return new TitleValue( $ns ?? NS_MAIN, $dbkey );
1755 }
1756 // Backward-compatibility with cache contents generated by MW < 1.46
1757 $dbkey = $this->getExtensionData( 'parsoid:title-dbkey' );
1758 if ( $dbkey !== null ) {
1759 // This is a prefixed DB key w/ localized namespace
1760 $titleFactory = MediaWikiServices::getInstance()->getTitleFactory();
1761 return $titleFactory->newFromDBkey( $dbkey );
1762 }
1763 return null;
1764 }
1765
1776 public function setTitle( ParsoidLinkTarget|PageReference $title ): void {
1777 if ( $title instanceof PageReference ) {
1778 $title->assertWiki( WikiAwareEntity::LOCAL );
1779 } else {
1780 Assert::invariant( !$title->isExternal(), "title should be local" );
1781 }
1782 $this->setExtensionData( 'core:title-ns', $title->getNamespace() );
1783 $this->setExtensionData( 'core:title-dbkey', $title->getDBkey() );
1784 }
1785
1824 public function getLanguage(): ?Bcp47Code {
1825 // This information is temporarily stored in extension data (T303329)
1826 $code = $this->getExtensionData( 'core:target-lang-variant' );
1827 // This is null if the ParserOutput was cached by MW 1.40 or earlier,
1828 // or not constructed by Parser/ParserCache.
1829 return $code === null ? null : new Bcp47CodeValue( $code );
1830 }
1831
1841 public function setLanguage( Bcp47Code $lang ): void {
1842 $this->setExtensionData( 'core:target-lang-variant', $lang->toBcp47Code() );
1843 }
1844
1851 public function getRedirectHeader(): ?string {
1852 return $this->getExtensionData( 'core:redirect-header' );
1853 }
1854
1859 public function setRedirectHeader( string $html ): void {
1860 $this->setExtensionData( 'core:redirect-header', $html );
1861 }
1862
1871 public function setRenderId( string $renderId ): void {
1872 $this->setExtensionData( 'core:render-id', $renderId );
1873 }
1874
1882 public function getRenderId(): ?string {
1883 // Backward-compatibility with old cache contents
1884 // Can be removed after parser cache contents have expired
1885 $old = $this->getExtensionData( 'parsoid-render-id' );
1886 if ( $old !== null ) {
1887 return ParsoidRenderId::newFromKey( $old )->getUniqueID();
1888 }
1889 return $this->getExtensionData( 'core:render-id' );
1890 }
1891
1897 public function getAllFlags(): array {
1898 // Before MW 1.46 this did not include NO_GALLERY, ENABLE_OOUI,
1899 // INDEX_POLICY, NO_INDEX_POLICY, NEW_SECTION, HIDE_NEW_SECTION,
1900 // and PREVENT_CLICKJACKING, but this method is only used internally.
1901 // See WikitextContentHandler::preSaveTransform() where this method
1902 // is used to transfer PST flags to the WikitextContent object, and
1903 // OutputPage::addParserOutputMetadata() where this method is used
1904 // to transfer ParserOutput flags to OutputPage::$mOutputFlags
1905 return array_keys( $this->mFlags );
1906 }
1907
2000 public function setPageProperty( string $name, string $value ): void {
2001 $this->setUnsortedPageProperty( $name, $value );
2002 }
2003
2018 public function setNumericPageProperty( string $propName, $numericValue ): void {
2019 if ( !is_numeric( $numericValue ) ) {
2020 throw new InvalidArgumentException( __METHOD__ . " with non-numeric value" );
2021 }
2022 // Coerce numeric sort key to a number.
2023 $this->mProperties[$propName] = 0 + $numericValue;
2024 }
2025
2043 public function setUnsortedPageProperty( string $propName, string $value = '' ): void {
2044 $this->mProperties[$propName] = $value;
2045 }
2046
2060 public function getPageProperty( string $name ) {
2061 return $this->mProperties[$name] ?? null;
2062 }
2063
2069 public function unsetPageProperty( string $name ): void {
2070 unset( $this->mProperties[$name] );
2071 }
2072
2078 public function getPageProperties(): array {
2079 return $this->mProperties;
2080 }
2081
2104 public function setOutputFlag( ParserOutputFlags|string $name, bool $val = true ): void {
2105 if ( is_string( $name ) ) {
2106 $flag = ParserOutputFlags::tryFrom( $name );
2107 if ( $flag === null ) {
2109 __METHOD__ . ' with non-standard flag', '1.45'
2110 );
2111 }
2112 } else {
2113 $flag = $name;
2114 $name = $flag->value;
2115 }
2116 if ( $val ) {
2117 $this->mFlags[$name] = true;
2118 } else {
2119 unset( $this->mFlags[$name] );
2120 }
2121 }
2122
2137 public function getOutputFlag( ParserOutputFlags|string $flag ): bool {
2138 // In the future we will return false if $flag doesn't correspond to a
2139 // valid ParserOutputFlag; see deprecation notice in ::setOutputFlag().
2140 $name = $flag instanceof ParserOutputFlags ? $flag->value : $flag;
2141 return $this->mFlags[$name] ?? false;
2142 }
2143
2155 public function appendOutputStrings( string|ParserOutputStringSets $name, array $value ): void {
2156 if ( is_string( $name ) ) {
2157 $name = ParserOutputStringSets::from( $name );
2158 }
2159 match ( $name ) {
2160 ParserOutputStringSets::MODULE =>
2161 $this->addModules( $value ),
2162 ParserOutputStringSets::MODULE_STYLE =>
2163 $this->addModuleStyles( $value ),
2164 ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC =>
2165 array_walk( $value, fn ( $v, $i ) =>
2166 $this->addExtraCSPDefaultSrc( $v )
2167 ),
2168 ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC =>
2169 array_walk( $value, fn ( $v, $i ) =>
2170 $this->addExtraCSPScriptSrc( $v )
2171 ),
2172 ParserOutputStringSets::EXTRA_CSP_STYLE_SRC =>
2173 array_walk( $value, fn ( $v, $i ) =>
2174 $this->addExtraCSPStyleSrc( $v )
2175 ),
2176 };
2177 }
2178
2191 public function getOutputStrings( string|ParserOutputStringSets $name ): array {
2192 if ( is_string( $name ) ) {
2193 $name = ParserOutputStringSets::from( $name );
2194 }
2195 return match ( $name ) {
2196 ParserOutputStringSets::MODULE =>
2197 $this->getModules(),
2198 ParserOutputStringSets::MODULE_STYLE =>
2199 $this->getModuleStyles(),
2200 ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC =>
2201 $this->getExtraCSPDefaultSrcs(),
2202 ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC =>
2203 $this->getExtraCSPScriptSrcs(),
2204 ParserOutputStringSets::EXTRA_CSP_STYLE_SRC =>
2205 $this->getExtraCSPStyleSrcs(),
2206 };
2207 }
2208
2258 public function setExtensionData( $key, $value ): void {
2259 if (
2260 array_key_exists( $key, $this->mExtensionData ) &&
2261 $this->mExtensionData[$key] !== $value
2262 ) {
2263 // This is discouraged, as it prevents selective update,
2264 // and was deprecated in 1.38.
2265 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
2266 }
2267 if ( $value === null ) {
2268 unset( $this->mExtensionData[$key] );
2269 } else {
2270 $this->mExtensionData[$key] = $value;
2271 }
2272 }
2273
2298 public function appendExtensionData(
2299 string $key,
2300 $value,
2301 MergeStrategy|string $strategy = MergeStrategy::UNION
2302 ): void {
2303 if ( is_string( $strategy ) ) {
2304 $strategy = MergeStrategy::from( $strategy );
2305 }
2306 $this->mExtensionData = self::mergeMapStrategy(
2307 $this->mExtensionData,
2308 [ $key => self::makeMapStrategy( $value, $strategy ) ]
2309 );
2310 }
2311
2323 public function getExtensionData( $key ) {
2324 $value = $this->mExtensionData[$key] ?? null;
2325 if ( is_array( $value ) ) {
2326 if ( ( $value[self::MW_MERGE_STRATEGY_KEY] ?? null ) === MergeStrategy::SUM->value ) {
2327 return $value['value'];
2328 }
2329 // Don't expose our internal merge strategy key.
2330 unset( $value[self::MW_MERGE_STRATEGY_KEY] );
2331 }
2332 return $value;
2333 }
2334
2335 private static function getTimes( ?string $clock = null ): array {
2336 $ret = [];
2337 if ( !$clock || $clock === 'wall' ) {
2338 $ret['wall'] = hrtime( true ) / 10 ** 9;
2339 }
2340 if ( !$clock || $clock === 'cpu' ) {
2341 $ru = getrusage( 0 /* RUSAGE_SELF */ );
2342 $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
2343 $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
2344 }
2345 return $ret;
2346 }
2347
2354 public function resetParseStartTime(): void {
2355 $this->mParseStartTime = self::getTimes();
2356 $this->mTimeProfile = [];
2357 }
2358
2368 public function clearParseStartTime(): void {
2369 $this->mParseStartTime = [];
2370 }
2371
2382 public function recordTimeProfile() {
2383 if ( !$this->mParseStartTime ) {
2384 // If resetParseStartTime was never called, there is nothing to record
2385 return;
2386 }
2387
2388 if ( $this->mTimeProfile !== [] ) {
2389 // Don't override the times recorded by the previous call to recordTimeProfile().
2390 return;
2391 }
2392
2393 $now = self::getTimes();
2394 $this->mTimeProfile = [
2395 'wall' => $now['wall'] - $this->mParseStartTime['wall'],
2396 'cpu' => $now['cpu'] - $this->mParseStartTime['cpu'],
2397 ];
2398 }
2399
2417 public function getTimeProfile( string $clock ) {
2418 return $this->mTimeProfile[ $clock ] ?? null;
2419 }
2420
2440 public function setLimitReportData( $key, $value ): void {
2441 $this->mLimitReportData[$key] = $value;
2442
2443 if ( is_array( $value ) ) {
2444 if ( array_keys( $value ) === [ 0, 1 ]
2445 && is_numeric( $value[0] )
2446 && is_numeric( $value[1] )
2447 ) {
2448 $data = [ 'value' => $value[0], 'limit' => $value[1] ];
2449 } else {
2450 $data = $value;
2451 }
2452 } else {
2453 $data = $value;
2454 }
2455
2456 if ( strpos( $key, '-' ) ) {
2457 [ $ns, $name ] = explode( '-', $key, 2 );
2458 $this->mLimitReportJSData[$ns][$name] = $data;
2459 } else {
2460 $this->mLimitReportJSData[$key] = $data;
2461 }
2462 }
2463
2480 public function hasReducedExpiry(): bool {
2481 if ( $this->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) ) {
2482 // If this page has async content, then we should re-run
2483 // RefreshLinksJob whenever we regenerate the page.
2484 return true;
2485 }
2486 $parserCacheExpireTime = MediaWikiServices::getInstance()->getMainConfig()->get(
2487 MainConfigNames::ParserCacheExpireTime );
2488
2489 // Deliberately not using ::getCacheExpiry() here, which can get
2490 // quantized in MiserMode.
2491 return ( $this->mCacheExpiry ?? $parserCacheExpireTime ) < $parserCacheExpireTime;
2492 }
2493
2495 public function getCacheExpiry(): int {
2496 $expiry = parent::getCacheExpiry();
2497 if ( $expiry <= 0 ) {
2498 // Uncacheable.
2499 // Note that this function should return 0 if and only if
2500 // ::isCacheable() returns false.
2501 return 0;
2502 }
2503 // Raise the minimum to ~24 hrs for content pages on large
2504 // wiki farms when MiserMode is enabled. (T416616)
2505 // Implemented as expiring around the next midnight. We take
2506 // both UTC midnight and midnight in a wiki-configured
2507 // timezone into account. On English-language and
2508 // multilingual wikis we get 24 hours (given UTC is the
2509 // timezone), and other wikis split as 1h/23h or down to
2510 // 12h/12h.
2511 $services = MediaWikiServices::getInstance();
2512 $config = $services->getMainConfig();
2513 if (
2514 $config->get( MainConfigNames::MiserMode ) &&
2515 $services->getNamespaceInfo()->isContent(
2516 $this->getTitle()?->getNamespace() ?? NS_MAIN
2517 )
2518 ) {
2519 $date = DateTimeImmutable::createFromInterface(
2520 MWTimestamp::fromMW( $this->getCacheTime() )->timestamp
2521 );
2522 // T419439: the deadline, skew, and stagger here should probably
2523 // be configurable.
2524 $utcMidnight = $date
2525 ->modify( 'next day midnight' )
2526 ->getTimestamp();
2527 $localMidnight = $date
2528 ->setTimeZone( new DateTimeZone(
2529 $config->get( MainConfigNames::Localtimezone )
2530 ) )
2531 ->modify( 'next day midnight' )
2532 ->getTimestamp();
2533 // Whichever comes first: UTC midnight, local midnight, or expiry
2534 $timeToNextMidnight = min( $utcMidnight, $localMidnight ) - $date->getTimestamp();
2535 // "Randomly" stagger expiry across a window to avoid cache
2536 // stampedes.
2537 $stagger = 60 * 60; // 1 hour
2538 if ( $timeToNextMidnight < ( $stagger / 2 ) ) {
2539 // Account for clock skew and unlucky parses just before
2540 // midnight by ensuring our "midnight" deadline is at least
2541 // half-a-stagger away (but halve the stagger in that case
2542 // so we never spread the expiry past midnight+stagger).
2543 $stagger /= 2;
2544 $timeToNextMidnight = $stagger;
2545 }
2546 // Stagger is a function of parse time, which should be random-ish.
2547 $timeToNextMidnight += ( $date->getTimestamp() % $stagger );
2548 $expiry = max( $expiry, $timeToNextMidnight );
2549 }
2550
2551 if ( $this->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY ) ) {
2552 $asyncExpireTime = $config->get(
2553 MainConfigNames::ParserCacheAsyncExpireTime
2554 );
2555 $expiry = max( 1, min( $expiry, $asyncExpireTime ) );
2556 }
2557 return $expiry;
2558 }
2559
2573 public function setPreventClickjacking( bool $flag ): void {
2574 $this->setOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING, $flag );
2575 }
2576
2585 public function getPreventClickjacking(): bool {
2586 return $this->getOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING );
2587 }
2588
2598 public function updateRuntimeAdaptiveExpiry( int $ttl, ?string $source = null ): void {
2599 $this->mMaxAdaptiveExpiry ??= $ttl;
2600 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
2601 $this->updateCacheExpiry( $ttl, $source );
2602 }
2603
2613 public function addExtraCSPDefaultSrc( $src ): void {
2614 $this->mExtraDefaultSrcs[] = $src;
2615 }
2616
2623 public function addExtraCSPStyleSrc( $src ): void {
2624 $this->mExtraStyleSrcs[] = $src;
2625 }
2626
2635 public function addExtraCSPScriptSrc( $src ): void {
2636 $this->mExtraScriptSrcs[] = $src;
2637 }
2638
2644 public function finalizeAdaptiveCacheExpiry(): void {
2645 if ( $this->mMaxAdaptiveExpiry === null ) {
2646 return; // not set
2647 }
2648
2649 $runtime = $this->getTimeProfile( 'wall' );
2650 if ( is_float( $runtime ) ) {
2651 $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
2652 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
2653 // SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
2654 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
2655
2656 $adaptiveTTL = intval( $slope * $runtime + $point );
2657 $adaptiveTTL = max( $adaptiveTTL, self::MIN_AR_TTL );
2658 $adaptiveTTL = min( $adaptiveTTL, $this->mMaxAdaptiveExpiry );
2659 $this->updateCacheExpiry( $adaptiveTTL, 'adaptive-ttl' );
2660 }
2661 }
2662
2667 public function setFromParserOptions( ParserOptions $parserOptions ) {
2668 // Copied from Parser.php::parse and should probably be abstracted
2669 // into the parent base class (probably as part of T236809)
2670 // Wrap non-interface parser output in a <div> so it can be targeted
2671 // with CSS (T37247)
2672 $class = $parserOptions->getWrapOutputClass();
2673 if ( $class !== false && !$parserOptions->isMessage() ) {
2674 $this->addWrapperDivClass( $class );
2675 }
2676
2677 // Record whether we should wrap sections for collapsing them
2678 if ( $parserOptions->getCollapsibleSections() ) {
2679 $this->setOutputFlag( ParserOutputFlags::COLLAPSIBLE_SECTIONS );
2680 }
2681
2682 // Record whether this is a preview parse in the output (T341010)
2683 if ( $parserOptions->getIsPreview() ) {
2684 $this->setOutputFlag( ParserOutputFlags::IS_PREVIEW, true );
2685 // Ensure that previews aren't cacheable, just to be safe.
2686 $this->updateCacheExpiry( 0, 'preview' );
2687 }
2688
2689 // Record whether this was parsed with the legacy parser
2690 // (Unlike some other options here, this does/should fork the cache.)
2691 if ( $parserOptions->getUseParsoid() ) {
2692 $this->setOutputFlag( ParserOutputFlags::USE_PARSOID, true );
2693 }
2694 }
2695
2703 $this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
2704 $this->mWarningMsgs = self::mergeMap( $this->mWarningMsgs, $source->mWarningMsgs );
2705 $this->mTimestamp = self::useMaxValue( $this->mTimestamp, $source->getRevisionTimestamp() );
2706 if ( $source->hasCacheTime() ) {
2707 $sourceCacheTime = $source->getCacheTime();
2708 if (
2709 !$this->hasCacheTime() ||
2710 // "undocumented use of -1 to mean not cacheable"
2711 // deprecated, but still supported by ::setCacheTime()
2712 strval( $sourceCacheTime ) === '-1' ||
2713 (
2714 strval( $this->getCacheTime() ) !== '-1' &&
2715 // use newer of the two times
2716 $this->getCacheTime() < $sourceCacheTime
2717 )
2718 ) {
2719 $this->setCacheTime( $sourceCacheTime );
2720 }
2721 }
2722 if ( $source->getRenderId() !== null ) {
2723 // Final render ID should be a function of all component POs
2724 $rid = ( $this->getRenderId() ?? '' ) . $source->getRenderId();
2725 $this->setRenderId( $rid );
2726 }
2727 if ( $source->getCacheRevisionId() !== null ) {
2728 $sourceCacheRevisionId = $source->getCacheRevisionId();
2729 $thisCacheRevisionId = $this->getCacheRevisionId();
2730 if ( $thisCacheRevisionId === null ) {
2731 $this->setCacheRevisionId( $sourceCacheRevisionId );
2732 } elseif ( $sourceCacheRevisionId !== $thisCacheRevisionId ) {
2733 // May throw an exception here in the future
2735 __METHOD__ . ": conflicting revision IDs " .
2736 "$thisCacheRevisionId and $sourceCacheRevisionId"
2737 );
2738 }
2739 }
2740 if ( $source->mCacheExpiry !== null ) {
2741 $this->updateCacheExpiry( $source->mCacheExpiry );
2742 }
2743
2744 foreach ( self::SPECULATIVE_FIELDS as $field ) {
2745 if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
2746 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
2747 }
2748 $this->$field = self::useMaxValue( $this->$field, $source->$field );
2749 }
2750
2751 $this->mParseStartTime = self::useEachMinValue(
2752 $this->mParseStartTime,
2753 $source->mParseStartTime
2754 );
2755
2756 $this->mTimeProfile = self::useEachTotalValue(
2757 $this->mTimeProfile,
2758 $source->mTimeProfile
2759 );
2760
2761 $this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
2762 $this->mParseUsedOptions = self::mergeMap( $this->mParseUsedOptions, $source->mParseUsedOptions );
2763
2764 // TODO: maintain per-slot limit reports!
2765 if ( !$this->mLimitReportData ) {
2766 $this->mLimitReportData = $source->mLimitReportData;
2767 }
2768 if ( !$this->mLimitReportJSData ) {
2769 $this->mLimitReportJSData = $source->mLimitReportJSData;
2770 }
2771 }
2772
2779 public function mergeHtmlMetaDataFrom( ParserOutput $source ): void {
2780 // HTML and HTTP
2781 $this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
2782 $this->addModules( $source->getModules() );
2783 $this->addModuleStyles( $source->getModuleStyles() );
2784 $this->mJsConfigVars = self::mergeMapStrategy( $this->mJsConfigVars, $source->mJsConfigVars );
2785 if ( $source->mMaxAdaptiveExpiry !== null ) {
2786 $this->updateRuntimeAdaptiveExpiry( $source->mMaxAdaptiveExpiry, $source->getCacheExpirySource() );
2787 }
2788 if ( $source->mCacheExpiry !== null ) {
2789 // Deliberately not using ::getCacheExpiry() here, which can get
2790 // quantized in MiserMode.
2791 $this->updateCacheExpiry( $source->mCacheExpiry, $source->getCacheExpirySource() );
2792 }
2793 $this->mExtraStyleSrcs = self::mergeList(
2794 $this->mExtraStyleSrcs,
2795 $source->getExtraCSPStyleSrcs()
2796 );
2797 $this->mExtraScriptSrcs = self::mergeList(
2798 $this->mExtraScriptSrcs,
2799 $source->getExtraCSPScriptSrcs()
2800 );
2801 $this->mExtraDefaultSrcs = self::mergeList(
2802 $this->mExtraDefaultSrcs,
2803 $source->getExtraCSPDefaultSrcs()
2804 );
2805
2806 foreach ( [
2807 // "noindex" always wins!
2808 ParserOutputFlags::INDEX_POLICY,
2809 ParserOutputFlags::NO_INDEX_POLICY,
2810 // Skin control
2811 ParserOutputFlags::NEW_SECTION,
2812 ParserOutputFlags::HIDE_NEW_SECTION,
2813 ParserOutputFlags::NO_GALLERY,
2814 ParserOutputFlags::ENABLE_OOUI,
2815 ParserOutputFlags::PREVENT_CLICKJACKING,
2816 // Selective update
2817 ParserOutputFlags::PREVENT_SELECTIVE_UPDATE,
2818 ] as $flag ) {
2819 // logical OR of $this and $source
2820 if ( $source->getOutputFlag( $flag ) ) {
2821 $this->setOutputFlag( $flag );
2822 }
2823 }
2824
2825 $tocData = $this->getTOCData();
2826 $sourceTocData = $source->getTOCData();
2827 if ( $tocData !== null ) {
2828 if ( $sourceTocData !== null ) {
2829 // T327429: Section merging is broken, since it doesn't respect
2830 // global numbering, but there are tests which expect section
2831 // metadata to be concatenated.
2832 // There should eventually be a deprecation warning here.
2833 foreach ( $sourceTocData->getSections() as $s ) {
2834 $tocData->addSection( $s );
2835 }
2836 }
2837 } elseif ( $sourceTocData !== null ) {
2838 $this->setTOCData( $sourceTocData );
2839 }
2840
2841 // XXX: we don't want to concatenate title text, so first write wins.
2842 // We should use the first *modified* title text, but we don't have the original to check.
2843 if ( $this->mTitleText === '' ) {
2844 $this->mTitleText = $source->mTitleText;
2845 }
2846
2847 // class names are stored in array keys
2848 $this->mWrapperDivClasses = self::mergeMap(
2849 $this->mWrapperDivClasses,
2850 $source->mWrapperDivClasses
2851 );
2852
2853 // NOTE: last write wins, same as within one ParserOutput
2854 foreach ( $source->getIndicators() as $id => $content ) {
2855 $this->setIndicator( $id, $content );
2856 }
2857
2858 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
2859 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
2860 // kinds of extension data to be merged in different ways.
2861 $this->mExtensionData = self::mergeMapStrategy(
2862 $this->mExtensionData,
2863 $source->mExtensionData
2864 );
2865 }
2866
2874 foreach ( ParserOutputLinkTypes::cases() as $linkType ) {
2875 foreach ( $source->getLinkList( $linkType ) as $linkItem ) {
2876 $this->appendLinkList( $linkType, $linkItem );
2877 }
2878 }
2879 $this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
2880
2881 // TODO: add a $mergeStrategy parameter to setPageProperty to allow different
2882 // kinds of properties to be merged in different ways.
2883 // (Model this after ::appendJsConfigVar(); use ::mergeMapStrategy here)
2884 $this->mProperties = self::mergeMap( $this->mProperties, $source->getPageProperties() );
2885
2886 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
2887 $this->mExtensionData = self::mergeMapStrategy(
2888 $this->mExtensionData,
2889 $source->mExtensionData
2890 );
2891 }
2892
2902 public function collectMetadata( ContentMetadataCollector $metadata ): void {
2903 // Uniform handling of all boolean flags: they are OR'ed together.
2904 $flags = array_keys(
2905 $this->mFlags + array_flip( ParserOutputFlags::values() )
2906 );
2907 foreach ( $flags as $name ) {
2908 $name = (string)$name;
2909 if ( $this->getOutputFlag( $name ) ) {
2910 $metadata->setOutputFlag( $name );
2911 }
2912 }
2913
2914 // Uniform handling of string sets: they are unioned.
2915 // (This includes modules, style modes, and CSP src.)
2916 foreach ( ParserOutputStringSets::values() as $name ) {
2917 $metadata->appendOutputStrings(
2918 $name, $this->getOutputStrings( $name )
2919 );
2920 }
2921
2922 foreach ( $this->mCategories as $cat => $key ) {
2923 // Numeric category strings are going to come out of the
2924 // `mCategories` array as ints; cast back to string.
2925 // Also convert back to a LinkTarget!
2926 $lt = TitleValue::tryNew( NS_CATEGORY, (string)$cat );
2927 $metadata->addCategory( $lt, $key );
2928 }
2929
2930 foreach ( $this->mLinks as $ns => $arr ) {
2931 foreach ( $arr as $dbk => $id ) {
2932 // Numeric titles are going to come out of the
2933 // `mLinks` array as ints; cast back to string.
2934 $lt = TitleValue::tryNew( $ns, (string)$dbk );
2935 $metadata->addLink( $lt, $id );
2936 }
2937 }
2938
2939 foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
2940 foreach ( $arr as $dbk => $ignore ) {
2941 $lt = TitleValue::tryNew( NS_MAIN, (string)$dbk, '', $prefix );
2942 $metadata->addLink( $lt );
2943 }
2944 }
2945
2946 foreach ( $this->mLinksSpecial as $dbk => $ignore ) {
2947 // Numeric titles are going to come out of the
2948 // `mLinksSpecial` array as ints; cast back to string.
2949 $lt = TitleValue::tryNew( NS_SPECIAL, (string)$dbk );
2950 $metadata->addLink( $lt );
2951 }
2952
2953 foreach ( $this->mImages as $name => $ignore ) {
2954 // Numeric titles come out of mImages as ints.
2955 $lt = TitleValue::tryNew( NS_FILE, (string)$name );
2956 $props = $this->mFileSearchOptions[$name] ?? [];
2957 $metadata->addImage( $lt, $props['time'] ?? null, $props['sha1'] ?? null );
2958 }
2959
2960 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
2961 # language links can have fragments!
2962 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
2963 $lt = TitleValue::tryNew( NS_MAIN, $title, $frag, (string)$lang );
2964 $metadata->addLanguageLink( $lt );
2965 }
2966
2967 foreach ( $this->mJsConfigVars as $key => $value ) {
2968 // Numeric keys and items are going to come out of the
2969 // `mJsConfigVars` array as ints; cast back to string.
2970 $key = (string)$key;
2971 if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
2972 self::collectMapStrategy( $value, static fn ( $v, $s ) =>
2973 $metadata->appendJsConfigVar( $key, $v, $s )
2974 );
2975 } elseif ( $metadata instanceof ParserOutput &&
2976 array_key_exists( $key, $metadata->mJsConfigVars )
2977 ) {
2978 // This behavior is deprecated, will likely result in
2979 // incorrect output, and we'll eventually emit a
2980 // warning here---but at the moment this is usually
2981 // caused by limitations in Parsoid and/or use of
2982 // the ParserAfterParse hook: T303015#7770480
2983 $metadata->mJsConfigVars[$key] = $value;
2984 $metadata->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
2985 } else {
2986 $metadata->setJsConfigVar( $key, $value );
2987 }
2988 }
2989 foreach ( $this->mExtensionData as $key => $value ) {
2990 // Numeric keys and items are going to come out of the array as
2991 // ints, cast back to string.
2992 $key = (string)$key;
2993 if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
2994 self::collectMapStrategy( $value, static fn ( $v, $s ) =>
2995 $metadata->appendExtensionData( $key, $v, $s )
2996 );
2997 } elseif ( $metadata instanceof ParserOutput &&
2998 array_key_exists( $key, $metadata->mExtensionData )
2999 ) {
3000 // This behavior is deprecated, will likely result in
3001 // incorrect output, and we'll eventually emit a
3002 // warning here---but at the moment this is usually
3003 // caused by limitations in Parsoid and/or use of
3004 // the ParserAfterParse hook: T303015#7770480
3005 $metadata->mExtensionData[$key] = $value;
3006 } else {
3007 $metadata->setExtensionData( $key, $value );
3008 }
3009 }
3010 foreach ( $this->mExternalLinks as $url => $ignore ) {
3011 $metadata->addExternalLink( (string)$url );
3012 }
3013 foreach ( $this->mProperties as $prop => $value ) {
3014 // Numeric properties are going to come out of the array as ints
3015 $prop = (string)$prop;
3016 if ( is_string( $value ) ) {
3017 $metadata->setUnsortedPageProperty( $prop, $value );
3018 } elseif ( is_numeric( $value ) ) {
3019 $metadata->setNumericPageProperty( $prop, $value );
3020 } else {
3021 // Deprecated, but there are still sites which call
3022 // ::setPageProperty() with "unusual" values (T374046)
3023 wfDeprecated( __METHOD__ . ' with unusual page property', '1.45' );
3024 }
3025 }
3026 foreach ( $this->mLimitReportData as $key => $value ) {
3027 $metadata->setLimitReportData( (string)$key, $value );
3028 }
3029 foreach ( $this->getIndicators() as $id => $content ) {
3030 $metadata->setIndicator( (string)$id, $content );
3031 }
3032
3033 // ParserOutput-only fields; maintained "behind the curtain"
3034 // since Parsoid doesn't have to know about them.
3035 //
3036 // In production use, the $metadata supplied to this method
3037 // will almost always be an instance of ParserOutput, passed to
3038 // Parsoid by core when parsing begins and returned to core by
3039 // Parsoid as a ContentMetadataCollector (Parsoid's name for
3040 // ParserOutput) when DataAccess::parseWikitext() is called.
3041 //
3042 // We may use still Parsoid's StubMetadataCollector for testing or
3043 // when running Parsoid in standalone mode, so forcing a downcast
3044 // here would lose some flexibility.
3045
3046 if ( $metadata instanceof ParserOutput ) {
3047 foreach ( $this->getUsedOptions() as $opt ) {
3048 $metadata->recordOption( $opt );
3049 }
3050 $metadata->mHeadItems = self::mergeMixedList(
3051 $metadata->mHeadItems, $this->mHeadItems
3052 );
3053 if ( $this->mMaxAdaptiveExpiry !== null ) {
3054 $metadata->updateRuntimeAdaptiveExpiry( $this->mMaxAdaptiveExpiry, $this->getCacheExpirySource() );
3055 }
3056 if ( $this->mCacheExpiry !== null ) {
3057 // Deliberately not using ::getCacheExpiry() here, which can get
3058 // quantized in MiserMode.
3059 $metadata->updateCacheExpiry( $this->mCacheExpiry, $this->getCacheExpirySource() );
3060 }
3061 if ( $this->mTimestamp !== null ) {
3062 $metadata->setRevisionTimestamp(
3063 self::useMaxValue(
3064 $this->mTimestamp, $metadata->getRevisionTimestamp()
3065 )
3066 );
3067 }
3068 if ( $this->mCacheTime !== '' ) {
3069 $metadata->setCacheTime( $this->mCacheTime );
3070 }
3071 if ( $this->mCacheRevisionId !== null ) {
3072 $metadata->setCacheRevisionId( $this->mCacheRevisionId );
3073 }
3074 // T293514: We should use the first *modified* title text, but
3075 // we don't have the original to check.
3076 $otherTitle = $metadata->getTitleText();
3077 if ( $otherTitle === '' ) {
3078 $metadata->setTitleText( $this->getTitleText() );
3079 }
3080 // class names are stored in array keys
3081 $metadata->mWrapperDivClasses = self::mergeMap(
3082 $metadata->mWrapperDivClasses,
3083 $this->mWrapperDivClasses
3084 );
3085 // T327429: Section merging is broken, since it doesn't respect
3086 // global numbering, but there are tests which expect section
3087 // metadata to be concatenated.
3088 // There should eventually be a deprecation warning here.
3089 $tocData = $this->getTOCData();
3090 $otherTocData = $metadata->getTOCData();
3091 if ( $otherTocData !== null ) {
3092 if ( $tocData !== null ) {
3093 foreach ( $tocData->getSections() as $s ) {
3094 $otherTocData->addSection( clone $s );
3095 }
3096 }
3097 } elseif ( $tocData !== null ) {
3098 $metadata->setTOCData( clone $tocData );
3099 }
3100 foreach (
3101 [
3102 ParserOutputLinkTypes::TEMPLATE,
3103 ParserOutputLinkTypes::EXISTENCE,
3104 ] as $linkType ) {
3105 foreach ( $this->getLinkList( $linkType ) as $linkItem ) {
3106 $metadata->appendLinkList( $linkType, $linkItem );
3107 }
3108 }
3109 foreach ( $this->mWarningMsgs as $key => $msg ) {
3110 $metadata->addWarningMsgVal( $msg, (string)$key );
3111 }
3112 // mWarnings is deprecated, but keep it around
3113 foreach ( $this->mWarnings as $str => $ignore ) {
3114 $metadata->mWarnings[$str] = 1;
3115 }
3116 // Final render ID should be a function of all component POs.
3117 // In order to make this symmetric w/r/t source and target,
3118 // use the lexicographically first one first.
3119 if ( $this->getRenderId() !== null ) {
3120 $renderIds = [
3121 $this->getRenderId(), $metadata->getRenderId() ?? ''
3122 ];
3123 sort( $renderIds, SORT_STRING );
3124 $metadata->setRenderId( implode( '', $renderIds ) );
3125 }
3126
3127 foreach ( self::SPECULATIVE_FIELDS as $field ) {
3128 if ( $this->$field && $metadata->$field && $this->$field !== $metadata->$field ) {
3129 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
3130 }
3131 $metadata->$field = self::useMaxValue( $this->$field, $metadata->$field );
3132 }
3133
3134 $metadata->mParseStartTime = self::useEachMinValue(
3135 $this->mParseStartTime,
3136 $metadata->mParseStartTime
3137 );
3138
3139 $metadata->mTimeProfile = self::useEachTotalValue(
3140 $this->mTimeProfile,
3141 $metadata->mTimeProfile
3142 );
3143 // TODO: maintain per-slot limit reports!
3144 if ( !$metadata->mLimitReportData ) {
3145 $metadata->mLimitReportData = $this->mLimitReportData;
3146 }
3147 if ( !$metadata->mLimitReportJSData ) {
3148 $metadata->mLimitReportJSData = $this->mLimitReportJSData;
3149 }
3150 }
3151 }
3152
3153 private static function mergeMixedList( array $a, array $b ): array {
3154 return array_unique( array_merge( $a, $b ), SORT_REGULAR );
3155 }
3156
3157 private static function mergeList( array $a, array $b ): array {
3158 return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
3159 }
3160
3161 private static function mergeMap( array $a, array $b ): array {
3162 return array_replace( $a, $b );
3163 }
3164
3168 private static function makeMapStrategy( string|int $value, MergeStrategy $strategy ): array {
3169 $base = [ self::MW_MERGE_STRATEGY_KEY => $strategy->value ];
3170 switch ( $strategy ) {
3171 case MergeStrategy::UNION:
3172 return [ $value => true, ...$base ];
3173 case MergeStrategy::SUM:
3174 Assert::parameterType( 'integer', $value, '$value' );
3175 return [ 'value' => $value, ...$base ];
3176 default:
3177 throw new InvalidArgumentException( "Unknown merge strategy {$strategy->value}" );
3178 }
3179 }
3180
3189 private static function collectMapStrategy( array $map, callable $f ): void {
3190 $strategy = MergeStrategy::from(
3191 $map[self::MW_MERGE_STRATEGY_KEY]
3192 );
3193 foreach ( $map as $key => $value ) {
3194 if ( $key === self::MW_MERGE_STRATEGY_KEY ) {
3195 continue;
3196 }
3197 switch ( $strategy ) {
3198 case MergeStrategy::UNION:
3199 $f( $key, $strategy ); // ignore value
3200 break;
3201 case MergeStrategy::SUM:
3202 $f( $value, $strategy ); // ignore key
3203 break;
3204 }
3205 }
3206 }
3207
3208 private static function mergeMapStrategy( array $a, array $b ): array {
3209 foreach ( $b as $key => $bValue ) {
3210 if ( !array_key_exists( $key, $a ) ) {
3211 $a[$key] = $bValue;
3212 } elseif (
3213 is_array( $a[$key] ) &&
3214 isset( $a[$key][self::MW_MERGE_STRATEGY_KEY] ) &&
3215 isset( $bValue[self::MW_MERGE_STRATEGY_KEY] )
3216 ) {
3217 $strategy = $bValue[self::MW_MERGE_STRATEGY_KEY];
3218 if ( $strategy !== $a[$key][self::MW_MERGE_STRATEGY_KEY] ) {
3219 throw new InvalidArgumentException( "Conflicting merge strategy for $key" );
3220 }
3221 $strategy = MergeStrategy::from( $strategy );
3222 switch ( $strategy ) {
3223 case MergeStrategy::UNION:
3224 // Note the array_merge is *not* safe to use here, because
3225 // the $bValue is expected to be a map from items to `true`.
3226 // If the item is a numeric string like '1' then array_merge
3227 // will convert it to an integer and renumber the array!
3228 $a[$key] = array_replace( $a[$key], $bValue );
3229 break;
3230 case MergeStrategy::SUM:
3231 $a[$key]['value'] += $b[$key]['value'];
3232 break;
3233 default:
3234 throw new InvalidArgumentException( "Unknown merge strategy {$strategy->value}" );
3235 }
3236 } else {
3237 $valuesSame = ( $a[$key] === $bValue );
3238 if ( ( !$valuesSame ) &&
3239 is_object( $a[$key] ) &&
3240 is_object( $bValue )
3241 ) {
3242 $jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
3243 $valuesSame = ( $jsonCodec->toJsonArray( $a[$key] ) === $jsonCodec->toJsonArray( $bValue ) );
3244 }
3245 if ( !$valuesSame ) {
3246 // Silently replace for now; in the future will first emit
3247 // a deprecation warning, and then (later) throw.
3248 $a[$key] = $bValue;
3249 }
3250 }
3251 }
3252 return $a;
3253 }
3254
3255 private static function useEachMinValue( array $a, array $b ): array {
3256 $values = [];
3257 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
3258
3259 foreach ( $keys as $k ) {
3260 $values[$k] = min( $a[$k] ?? INF, $b[$k] ?? INF );
3261 }
3262
3263 return $values;
3264 }
3265
3266 private static function useEachTotalValue( array $a, array $b ): array {
3267 $values = [];
3268 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
3269
3270 foreach ( $keys as $k ) {
3271 $values[$k] = ( $a[$k] ?? 0 ) + ( $b[$k] ?? 0 );
3272 }
3273
3274 return $values;
3275 }
3276
3282 private static function useMaxValue( $a, $b ) {
3283 if ( $a === null ) {
3284 return $b;
3285 }
3286
3287 if ( $b === null ) {
3288 return $a;
3289 }
3290
3291 return max( $a, $b );
3292 }
3293
3300 public function toJsonArray(): array {
3301 // WARNING: When changing how this class is serialized, follow the instructions
3302 // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
3303 $data = [
3304 'LanguageLinks' => $this->getLanguageLinksInternal(),
3305 'Categories' => $this->mCategories,
3306 'IndicatorIds' => $this->mIndicatorIds,
3307 'TitleText' => $this->mTitleText,
3308 'Links' => $this->mLinks,
3309 'LinksSpecial' => $this->mLinksSpecial,
3310 'Templates' => $this->mTemplates,
3311 'TemplateIds' => $this->mTemplateIds,
3312 'Images' => $this->mImages,
3313 'FileSearchOptions' => $this->mFileSearchOptions,
3314 'ExternalLinks' => $this->mExternalLinks,
3315 'InterwikiLinks' => $this->mInterwikiLinks,
3316 'ExistenceLinks' => $this->existenceLinks,
3317 'HeadItems' => $this->mHeadItems,
3318 'Modules' => array_keys( $this->mModuleSet ),
3319 'ModuleStyles' => array_keys( $this->mModuleStyleSet ),
3320 'JsConfigVars' => $this->mJsConfigVars,
3321 'Warnings' => $this->mWarnings,
3322 'WarningMsgs' => $this->mWarningMsgs,
3323 'TOCData' => $this->mTOCData,
3324 'Properties' => self::detectAndEncodeBinary( $this->mProperties ),
3325 'Timestamp' => $this->mTimestamp,
3326 // may contain arbitrary structures!
3327 'ExtensionData' => $this->mExtensionData,
3328 'LimitReportData' => $this->mLimitReportData,
3329 'LimitReportJSData' => $this->mLimitReportJSData,
3330 'CacheMessage' => $this->mCacheMessage,
3331 'TimeProfile' => $this->mTimeProfile,
3332 'ParseStartTime' => [], // don't serialize this
3333 'ExtraScriptSrcs' => $this->mExtraScriptSrcs,
3334 'ExtraDefaultSrcs' => $this->mExtraDefaultSrcs,
3335 'ExtraStyleSrcs' => $this->mExtraStyleSrcs,
3336 'SpeculativeRevId' => $this->mSpeculativeRevId,
3337 'SpeculativePageIdUsed' => $this->speculativePageIdUsed,
3338 'RevisionTimestampUsed' => $this->revisionTimestampUsed,
3339 'RevisionUsedSha1Base36' => $this->revisionUsedSha1Base36,
3340 'WrapperDivClasses' => $this->mWrapperDivClasses,
3341 'OutputFlags' => array_keys( $this->mFlags ),
3342 ];
3343 if ( $this->getContentHolder()->hasContent() ) {
3344 $data[ 'ContentHolder' ] = $this->getContentHolder();
3345 }
3346
3347 // Fill in missing fields from parents. Array addition does not override existing fields.
3348 $data += parent::toJsonArray();
3349
3350 // TODO: make more fields optional!
3351
3352 if ( $this->mMaxAdaptiveExpiry !== null ) {
3353 $data['MaxAdaptiveExpiry'] = $this->mMaxAdaptiveExpiry;
3354 }
3355
3356 return $data;
3357 }
3358
3359 public static function newFromJsonArray( array $json ): ParserOutput {
3360 $parserOutput = new ParserOutput();
3361 $parserOutput->initFromJson( $json );
3362 return $parserOutput;
3363 }
3364
3366 public static function jsonClassHintFor( string $keyName ) {
3367 return match ( $keyName ) {
3368 'TOCData' => Hint::build( TOCData::class, Hint::ONLY_FOR_DECODE ),
3369 'WarningMsgs' => Hint::build( MessageValue::class, Hint::LIST, Hint::ONLY_FOR_DECODE ),
3370 'ContentHolder' => Hint::build( ContentHolder::class ),
3371 default => null,
3372 };
3373 }
3374
3379 protected function initFromJson( array $jsonData ): void {
3380 parent::initFromJson( $jsonData );
3381
3382 // WARNING: When changing how this class is serialized, follow the instructions
3383 // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
3384 // (This includes changing default values when fields are missing.)
3385 if ( isset( $jsonData['ContentHolder'] ) ) {
3386 $this->contentHolder = $jsonData['ContentHolder'];
3387 } else {
3388 // mostly backward compatibility T423701
3389 $pageBundleData = $jsonData['ExtensionData'][self::PARSOID_PAGE_BUNDLE_KEY] ?? null;
3390 if ( $pageBundleData ) {
3391 unset( $jsonData['ExtensionData'][self::PARSOID_PAGE_BUNDLE_KEY] );
3392 $pb = HtmlPageBundle::newFromJsonArray(
3393 $pageBundleData + [ 'html' => $jsonData['Text'] ?? '' ]
3394 );
3395 $siteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
3396 $this->contentHolder = ContentHolder::createFromParsoidPageBundle( $pb, $siteConfig );
3397 } else {
3398 $this->contentHolder = ContentHolder::createFromLegacyString( $jsonData['Text'] ?? '' );
3399 }
3400 if ( !isset( $jsonData['Text'] ) ) {
3401 // Make the content holder empty if 'no content holder and 'Text' was null.
3402 $this->contentHolder->setAsHtmlString( ContentHolder::BODY_FRAGMENT, null );
3403 }
3404 }
3405 $this->mLanguageLinkMap = [];
3406 foreach ( ( $jsonData['LanguageLinks'] ?? [] ) as $l ) {
3407 // T374736: old serialized parser cache entries may
3408 // contain invalid language links; drop them quietly.
3409 // (This code can be removed two LTS releases past 1.45.)
3410 if ( str_contains( $l, ':' ) ) {
3411 $this->addLanguageLink( $l );
3412 }
3413 }
3414 // Default values should match the property default values.
3415 $this->mCategories = $jsonData['Categories'] ?? [];
3416 $this->mIndicatorIds = $jsonData['IndicatorIds'] ?? [];
3417 // backwards compatibility T427622
3418 foreach ( ( $jsonData['Indicators'] ?? [] ) as $id => $value ) {
3419 $this->setIndicator( $id, $value );
3420 }
3421 $this->mTitleText = $jsonData['TitleText'] ?? '';
3422 $this->mLinks = $jsonData['Links'] ?? [];
3423 $this->mLinksSpecial = $jsonData['LinksSpecial'] ?? [];
3424 $this->mTemplates = $jsonData['Templates'] ?? [];
3425 $this->mTemplateIds = $jsonData['TemplateIds'] ?? [];
3426 $this->mImages = $jsonData['Images'] ?? [];
3427 $this->mFileSearchOptions = $jsonData['FileSearchOptions'] ?? [];
3428 $this->mExternalLinks = $jsonData['ExternalLinks'] ?? [];
3429 $this->mInterwikiLinks = $jsonData['InterwikiLinks'] ?? [];
3430 $this->existenceLinks = $jsonData['ExistenceLinks'] ?? [];
3431 $this->mHeadItems = $jsonData['HeadItems'] ?? [];
3432 $this->mModuleSet = array_fill_keys( $jsonData['Modules'] ?? [], true );
3433 $this->mModuleStyleSet = array_fill_keys( $jsonData['ModuleStyles'] ?? [], true );
3434 $this->mJsConfigVars = $jsonData['JsConfigVars'] ?? [];
3435 $this->mWarnings = $jsonData['Warnings'] ?? [];
3436 $this->mWarningMsgs = $jsonData['WarningMsgs'] ?? [];
3437
3438 // Set flags stored as properties (backward compatibility with MW<1.45)
3439 $this->mFlags = $jsonData['Flags'] ?? [];
3440 if ( $jsonData['NoGallery'] ?? false ) {
3441 $this->setOutputFlag( ParserOutputFlags::NO_GALLERY );
3442 }
3443 if ( $jsonData['EnableOOUI'] ?? false ) {
3444 $this->setOutputFlag( ParserOutputFlags::ENABLE_OOUI );
3445 }
3446 $this->setIndexPolicy( $jsonData['IndexPolicy'] ?? '' );
3447 if ( $jsonData['NewSection'] ?? false ) {
3448 $this->setOutputFlag( ParserOutputFlags::NEW_SECTION );
3449 }
3450 if ( $jsonData['HideNewSection'] ?? false ) {
3451 $this->setOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION );
3452 }
3453 if ( $jsonData['PreventClickjacking'] ?? false ) {
3454 $this->setOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING );
3455 }
3456 // Set all generic output flags (whether stored as properties or not)
3457 // (This is effectively a logical-OR if these are also serialized
3458 // above.)
3459 foreach ( $jsonData['OutputFlags'] ?? [] as $flagName ) {
3460 $flag = ParserOutputFlags::tryFrom( $flagName );
3461 if ( $flag !== null ) {
3462 $this->setOutputFlag( $flag );
3463 } else {
3464 // T417819: We *should* backport new ParserOutputFlags values
3465 // to avoid reaching this case, but it ought to be safe to drop
3466 // the unknown flags on the floor.
3468 __METHOD__ . " of flag $flagName without forward compatibility",
3469 '1.46'
3470 );
3471 // Preserve non-standard flags for now since they are used in
3472 // serialization test cases.
3473 $this->setOutputFlag( $flagName );
3474 }
3475 }
3476
3477 if ( isset( $jsonData['TOCData'] ) ) {
3478 $this->mTOCData = $jsonData['TOCData'];
3479 // Backward-compatibility with old TOCData encoding (T327439)
3480 // emitted in MW < 1.45
3481 } elseif (
3482 ( $jsonData['Sections'] ?? [] ) !== [] ||
3483 // distinguish "no sections" from "sections not set"
3484 $this->getOutputFlag( 'mw:toc-set' )
3485 ) {
3486 $this->setSections( $jsonData['Sections'] ?? [] );
3487 unset( $this->mFlags['mw:toc-set'] );
3488 if ( isset( $jsonData['TOCExtensionData'] ) ) {
3489 $tocData = $this->getTOCData(); // created by setSections() above
3490 foreach ( $jsonData['TOCExtensionData'] as $key => $value ) {
3491 $tocData->setExtensionData( (string)$key, $value );
3492 }
3493 }
3494 }
3495 // backward-compatibility: convert page properties to their
3496 // 'database representation'. We haven't permitted non-string
3497 // non-numeric values since 1.45.
3498 $this->mProperties = [];
3499 foreach (
3500 self::detectAndDecodeBinary( $jsonData['Properties'] ?? [] )
3501 as $k => $v
3502 ) {
3503 if ( is_int( $v ) || is_float( $v ) || is_string( $v ) ) {
3504 $this->mProperties[$k] = $v;
3505 } elseif ( is_bool( $v ) ) {
3506 $this->mProperties[$k] = (int)$v;
3507 } elseif ( $v === null ) {
3508 $this->mProperties[$k] = '';
3509 } elseif ( is_array( $v ) ) {
3510 $this->mProperties[$k] = 'Array';
3511 } else {
3512 $this->mProperties[$k] = strval( $v );
3513 }
3514 }
3515 $this->mTimestamp = $jsonData['Timestamp'] ?? null;
3516 $this->mExtensionData = $jsonData['ExtensionData'] ?? [];
3517 $this->mLimitReportData = $jsonData['LimitReportData'] ?? [];
3518 $this->mLimitReportJSData = $jsonData['LimitReportJSData'] ?? [];
3519 $this->mCacheMessage = $jsonData['CacheMessage'] ?? '';
3520 $this->mParseStartTime = []; // invalid after reloading
3521 $this->mTimeProfile = $jsonData['TimeProfile'] ?? [];
3522 $this->mExtraScriptSrcs = $jsonData['ExtraScriptSrcs'] ?? [];
3523 $this->mExtraDefaultSrcs = $jsonData['ExtraDefaultSrcs'] ?? [];
3524 $this->mExtraStyleSrcs = $jsonData['ExtraStyleSrcs'] ?? [];
3525 $this->mSpeculativeRevId = $jsonData['SpeculativeRevId'] ?? null;
3526 $this->speculativePageIdUsed = $jsonData['SpeculativePageIdUsed'] ?? null;
3527 $this->revisionTimestampUsed = $jsonData['RevisionTimestampUsed'] ?? null;
3528 $this->revisionUsedSha1Base36 = $jsonData['RevisionUsedSha1Base36'] ?? null;
3529 $this->mWrapperDivClasses = $jsonData['WrapperDivClasses'] ?? [];
3530 $this->mMaxAdaptiveExpiry = $jsonData['MaxAdaptiveExpiry'] ?? null;
3531 }
3532
3542 private static function detectAndEncodeBinary( array $properties ) {
3543 foreach ( $properties as $key => $value ) {
3544 if ( is_string( $value ) ) {
3545 if ( !mb_detect_encoding( $value, 'UTF-8', true ) ) {
3546 $properties[$key] = [
3547 // T313818: This key name conflicts with JsonCodec
3548 '_type_' => 'string',
3549 '_encoding_' => 'base64',
3550 '_data_' => base64_encode( $value ),
3551 ];
3552 }
3553 }
3554 }
3555
3556 return $properties;
3557 }
3558
3567 private static function detectAndDecodeBinary( array $properties ) {
3568 foreach ( $properties as $key => $value ) {
3569 if ( is_array( $value ) && isset( $value['_encoding_'] ) ) {
3570 if ( $value['_encoding_'] === 'base64' ) {
3571 $properties[$key] = base64_decode( $value['_data_'] );
3572 }
3573 }
3574 }
3575
3576 return $properties;
3577 }
3578
3579 public function __serialize(): array {
3580 // Support for PHP serialization of ParserOutput for ParserCache
3581 // was turned off in 1.39 and is not guaranteed to work.
3582 wfDeprecated( "PHP serialization of ParserOutput", "1.39" );
3583 return (array)$this;
3584 }
3585
3586 public function __clone() {
3587 // It seems that very little of this object needs to be explicitly deep-cloned
3588 // while keeping copies reasonably separated.
3589 // Most of the non-scalar properties of this object are either
3590 // - (potentially multi-nested) arrays of scalars (which get deep-cloned), or
3591 // - arrays that may contain arbitrary elements (which don't necessarily get
3592 // deep-cloned), but for which no particular care elsewhere is given to
3593 // copying their references around (e.g. mJsConfigVars).
3594 // Hence, we are not going out of our way to ensure that the references to innermost
3595 // objects that may appear in a ParserOutput are unique. If that becomes the
3596 // expectation at any point, this method will require updating as well.
3597 // The exception is TOCData (which is an object), which we clone explicitly.
3598 if ( $this->mTOCData ) {
3599 $this->mTOCData = clone $this->mTOCData;
3600 }
3601 $this->contentHolder = clone $this->contentHolder;
3602 }
3603
3611 public function getContentHolderText(): string {
3612 $html = $this->contentHolder->getAsHtmlString( ContentHolder::BODY_FRAGMENT );
3613 if ( $html === null ) {
3614 throw new LogicException( 'This ParserOutput contains no text!' );
3615 }
3616 return $html;
3617 }
3618
3629 public function setContentHolderText( ?string $text ): void {
3630 $this->contentHolder->setAsHtmlString( ContentHolder::BODY_FRAGMENT, $text );
3631 }
3632
3634 private static function normalizeForObjectEquality(): array {
3635 return [
3636 'mFlags' => static function ( $v ) {
3637 ksort( $v );
3638 return $v;
3639 },
3640 ];
3641 }
3642}
3643
3645class_alias( ParserOutput::class, 'ParserOutput' );
const NS_FILE
Definition Defines.php:57
const NS_MAIN
Definition Defines.php:51
const NS_SPECIAL
Definition Defines.php:40
const NS_MEDIA
Definition Defines.php:39
const NS_CATEGORY
Definition Defines.php:65
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.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
Represents the identity of a specific rendering of a specific revision at some point in time.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
This is one of the Core classes and should be read at least once by any new developers.
getModules( $filter=false,... $args)
Get the list of modules to include on this page.
getJsConfigVars()
Get the javascript config vars to include on this page.
getModuleStyles( $filter=false,... $args)
Get the list of style-only modules to load on this page.
Parser cache specific expiry check.
Definition CacheTime.php:25
static createFromLegacyString(string $html)
Create a ContentHolder from a legacy body HTML string, typically returned by the legacy parser.
static createEmpty()
Creates an empty ContentHolder that can be used as a placeholder.
Set options of the Parser.
getWrapOutputClass()
Class to use to wrap output from Parser::parse()
getIsPreview()
Parsing the page for a "preview" operation?
getUseParsoid()
Parsoid-format HTML output, or legacy wikitext parser HTML?
getCollapsibleSections()
Should section contents be wrapped in.
ParserOutput is a rendering of a Content object or a message.
getExtraCSPDefaultSrcs()
Get extra Content-Security-Policy 'default-src' directives.
setIndexPolicy( $policy)
Update the index policy of the robots meta tag.
static jsonClassHintFor(string $keyName)
getJsConfigVars(bool $showStrategyKeys=false)
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
setEnableOOUI(bool $enable=false)
Enables OOUI, if true, in any OutputPage instance this ParserOutput object is added to.
toJsonArray()
Returns a JSON serializable structure representing this ParserOutput instance.
unsetPageProperty(string $name)
Remove a page property.
getTimeProfile(string $clock)
Returns the time that elapsed between the most recent call to resetParseStartTime() and the first cal...
appendExtensionData(string $key, $value, MergeStrategy|string $strategy=MergeStrategy::UNION)
Appends arbitrary data to this ParserObject.
addImage( $name, $timestamp=null, $sha1=null)
Register a file dependency for this output.
setNumericPageProperty(string $propName, $numericValue)
Set a numeric page property whose value is intended to be sorted and indexed.
runOutputPipeline(ParserOptions $popts, array $options=[])
appendJsConfigVar(string $key, $value, MergeStrategy|string $strategy=MergeStrategy::UNION)
Append a value to a variable to be set in mw.config in JavaScript.
getRenderId()
Return the unique rendering id for this ParserOutput.
addTemplate( $link, $page_id, $rev_id)
Register a template dependency for this output.
addExtraCSPDefaultSrc( $src)
Add an extra value to Content-Security-Policy default-src directive.
setExtensionData( $key, $value)
Attaches arbitrary data to this ParserObject.
setRenderId(string $renderId)
Store a unique rendering id for this ParserOutput.
addOutputPageMetadata(OutputPage $out)
Accommodate very basic transcluding of a temporary OutputPage object into parser output.
addWarningMsgVal(MessageSpecifier $mv, ?string $key=null)
Add a warning to the output for this page.
setIndicatorDom(string $id, DocumentFragment $content)
addExistenceDependency(ParsoidLinkTarget $link)
Add a dependency on the existence of a page.
setUnsortedPageProperty(string $propName, string $value='')
Set a page property whose value is not intended to be sorted and indexed.
getOutputStrings(string|ParserOutputStringSets $name)
Provides a uniform interface to various boolean string sets stored in the ParserOutput.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
setRedirectHeader(string $html)
Set an HTML prefix to be applied on redirect pages.
addCategory( $c, $sort='')
Add a category.
setJsConfigVar(string $key, $value)
Add a variable to be set in mw.config in JavaScript.
getPageProperties()
Return all the page properties set on this ParserOutput.
getContentHolderText()
Returns the body fragment text of the ParserOutput.
setRevisionTimestamp(?string $timestamp)
clearWrapperDivClass()
Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div until addWrap...
getPreventClickjacking()
Get the prevent-clickjacking flag.
setPreventClickjacking(bool $flag)
Set the prevent-clickjacking flag.
getOutputFlag(ParserOutputFlags|string $flag)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
addExtraCSPScriptSrc( $src)
Add an extra value to Content-Security-Policy script-src directive.
getExtraCSPStyleSrcs()
Get extra Content-Security-Policy 'style-src' directives.
getCategorySortKey(string $name)
Return the sort key for a given category name, or null if the category is not present in this ParserO...
getWrapperDivClass()
Returns the class (or classes) to be used with the wrapper div for this output.
const MW_MERGE_STRATEGY_UNION
Merge strategy to use for ParserOutput accumulators: "union" means that values are strings,...
setRawText(?string $text)
Set the raw text of the ParserOutput.
hasReducedExpiry()
Check whether the cache TTL was lowered from the site default.
getLinkList(string|ParserOutputLinkTypes $linkType, ?int $onlyNamespace=null)
Get a list of links of the given type.
addLanguageLink( $t)
Add a language link.
hasImages()
Return true if there are image dependencies registered for this ParserOutput.
addWarningMsg(string $msg,... $args)
Add a warning to the output for this page.
hasLinks()
Return true if the given parser output has local links registered in the metadata.
mergeHtmlMetaDataFrom(ParserOutput $source)
Merges HTML metadata such as head items, JS config vars, and HTTP cache control info from $source int...
recordTimeProfile()
Record the time since resetParseStartTime() was last called.
appendLinkList(string|ParserOutputLinkTypes $linkType, array $linkItem)
Append a link of the given type.
getDisplayTitle()
Get the title to be used for display.
setCategories(array $c)
Overwrite the category map.
getPageProperty(string $name)
Look up a page property.
setContentHolderText(?string $text)
Sets the body fragment text of the ParserOutput.
finalizeAdaptiveCacheExpiry()
Call this when parsing is done to lower the TTL based on low parse times.
getTitle()
Get the page used as context for creating this output.
getCategoryNames()
Return the names of the categories on this page.
mergeTrackingMetaDataFrom(ParserOutput $source)
Merges dependency tracking metadata such as backlinks, images used, and extension data from $source i...
getLanguage()
Get the primary language code of the output.
setSections(array $sectionArray)
__construct(?string $text=null, array $languageLinks=[], array $categoryLinks=[], $unused=false, string $titletext='')
addExtraCSPStyleSrc( $src)
Add an extra value to Content-Security-Policy style-src directive.
static newFromJsonArray(array $json)
mergeInternalMetaDataFrom(ParserOutput $source)
Merges internal metadata such as flags, accessed options, and profiling info from $source into this P...
addCacheMessage(string $msg)
Adds a comment notice about cache state to the text of the page.
getContentHolder()
Return the ContentHolder storing the HTML/DOM contents of this ParserOutput.
setLanguage(Bcp47Code $lang)
Set the primary language of the output.
getCategoryMap()
Return category names and sort keys as a map.
hasText()
Returns true if text was passed to the constructor, or set using setText().
setContentHolder(ContentHolder $contentHolder)
addHeadItem( $section, $tag=false)
Add some text to the "<head>".
getExtraCSPScriptSrcs()
Get extra Content-Security-Policy 'script-src' directives.
clearParseStartTime()
Unset the parse start time.
resetParseStartTime()
Resets the parse start timestamps for future calls to getTimeProfile() and recordTimeProfile().
updateRuntimeAdaptiveExpiry(int $ttl, ?string $source=null)
Lower the runtime adaptive TTL to at most this value.
setText( $text)
Set the raw text of the ParserOutput.
getExtensionData( $key)
Gets extensions data previously attached to this ParserOutput using setExtensionData().
getCacheExpiry()
Returns the number of seconds after which this object should expire.This method is used by ParserCach...
getRedirectHeader()
Return an HTML prefix to be applied on redirect pages, or null if this is not a redirect.
setFromParserOptions(ParserOptions $parserOptions)
Transfer parser options which affect post-processing from ParserOptions to this ParserOutput.
getRawText()
Get the cacheable text with <mw:editsection> markers still in it.
setOutputFlag(ParserOutputFlags|string $name, bool $val=true)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
initFromJson(array $jsonData)
Initialize member fields from an array returned by jsonSerialize().
collectMetadata(ContentMetadataCollector $metadata)
Adds the metadata collected in this ParserOutput to the supplied ContentMetadataCollector.
appendOutputStrings(string|ParserOutputStringSets $name, array $value)
Provides a uniform interface to various string sets stored in the ParserOutput.
addWrapperDivClass( $class)
Add a CSS class to use for the wrapping div.
static isLinkInternal( $internal, $url)
Checks, if a url is pointing to the own server.
setTitle(ParsoidLinkTarget|PageReference $title)
Sets the page context used to create this output.
setPageProperty(string $name, string $value)
Set a page property to be stored in the page_props database table.
setDisplayTitle(string $text)
Override the title to be used for display.
Represents the target of a wiki link.
Library for creating and parsing MW-style timestamps.
Value object representing a message for i18n.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestModuleOverrides' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'RestModuleOverrides' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Marker interface for entities aware of the wiki they belong to.
assertWiki( $wikiId)
Throws if $wikiId is different from the return value of getWikiId().
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
getKey()
Returns the message key.
$source