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;
30use Wikimedia\JsonCodec\Hint;
33use Wikimedia\Parsoid\Core\ContentMetadataCollector;
34use Wikimedia\Parsoid\Core\ContentMetadataCollectorCompat;
35use Wikimedia\Parsoid\Core\HtmlPageBundle;
36use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
37use Wikimedia\Parsoid\Core\MergeStrategy;
38use Wikimedia\Parsoid\Core\TOCData;
39use Wikimedia\Parsoid\DOM\DocumentFragment;
40
91class ParserOutput extends CacheTime implements ContentMetadataCollector {
92 // This is used to break cyclic dependencies and allow a measure
93 // of compatibility when new methods are added to ContentMetadataCollector
94 // by Parsoid.
95 use ContentMetadataCollectorCompat;
96
101 public const PARSOID_PAGE_BUNDLE_KEY = 'parsoid-page-bundle';
102
107 public const MW_MERGE_STRATEGY_KEY = '_mw-strategy';
108
120 public const MW_MERGE_STRATEGY_UNION = MergeStrategy::UNION;
121
122 private ContentHolder $contentHolder;
123
127 private array $mLanguageLinkMap = [];
128
132 private array $mCategories = [];
133
139 private array $mIndicatorIds = [];
140
144 private string $mTitleText;
145
152 private ?array $mDisplayTitleParts = null;
153
158 private array $mLinks = [];
159
164 private array $mLinksSpecial = [];
165
170 private array $mTemplates = [];
171
176 private array $mTemplateIds = [];
177
181 private array $mImages = [];
182
186 private array $mFileSearchOptions = [];
187
191 private array $mExternalLinks = [];
192
197 private array $mInterwikiLinks = [];
198
202 private array $existenceLinks = [];
203
207 private array $mHeadItems = [];
208
212 private array $mModuleSet = [];
213
217 private array $mModuleStyleSet = [];
218
222 private array $mJsConfigVars = [];
223
229 private array $mWarnings = [];
230
235 private array $mWarningMsgs = [];
236
240 private ?TOCData $mTOCData = null;
241
245 private array $mProperties = [];
246
250 private ?string $mTimestamp = null;
251
255 private array $mExtensionData = [];
256
260 private array $mLimitReportData = [];
261
263 private array $mLimitReportJSData = [];
264
266 private string $mCacheMessage = '';
267
271 private array $mParseStartTime = [];
272
276 private array $mTimeProfile = [];
277
281 private array $mExtraScriptSrcs = [];
282
286 private array $mExtraDefaultSrcs = [];
287
291 private array $mExtraStyleSrcs = [];
292
296 private $mFlags = [];
297
298 private const SPECULATIVE_FIELDS = [
299 'speculativePageIdUsed',
300 'mSpeculativeRevId',
301 'revisionTimestampUsed',
302 ];
303
305 private ?int $mSpeculativeRevId = null;
307 private ?int $speculativePageIdUsed = null;
309 private ?string $revisionTimestampUsed = null;
310
312 private ?string $revisionUsedSha1Base36 = null;
313
318 private array $mWrapperDivClasses = [];
319
324 private ?int $mMaxAdaptiveExpiry = null;
325
326 // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
327 // Current values imply that m=3933.333333 and b=-333.333333
328 // See https://www.nngroup.com/articles/website-response-times/
329 private const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
330 private const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
331 private const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
332 private const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
333 private const MIN_AR_TTL = 15; // min adaptive TTL (for pool counter, and edit stashing)
334
345 public function __construct( ?string $text = null, array $languageLinks = [], array $categoryLinks = [],
346 $unused = false, string $titletext = ''
347 ) {
348 if ( $text === null ) {
349 $this->contentHolder = ContentHolder::createEmpty();
350 } else {
351 $this->contentHolder = ContentHolder::createFromLegacyString( $text );
352 }
353 $this->mCategories = $categoryLinks;
354 $this->mTitleText = $titletext;
355 foreach ( $languageLinks as $ll ) {
356 $this->addLanguageLink( $ll );
357 }
358 // If the content handler does not specify an alternative (by
359 // calling ::resetParseStartTime() at a later point) then use
360 // the creation of the ParserOutput as the "start of parse" time.
361 $this->resetParseStartTime();
362 }
363
370 public function getContentHolder(): ContentHolder {
371 return $this->contentHolder;
372 }
373
378 public function setContentHolder( ContentHolder $contentHolder ) {
379 $this->contentHolder = $contentHolder;
380 }
381
392 public function hasText(): bool {
393 return $this->contentHolder->has( ContentHolder::BODY_FRAGMENT );
394 }
395
396 /*
397 * @unstable This method is transitional and will be replaced by a method
398 * in another class, maybe ContentRenderer. It allows us to break our
399 * porting work into two steps; in the first we bring ParserOptions to
400 * to each callsite to ensure it is made available to the
401 * postprocessing pipeline. In the second we move this functionality
402 * into the Content hierarchy and out of ParserOutput, which should become
403 * a pure value object.
404 *
405 * @param ParserOptions $popts
406 * @param array $options (since 1.31) Transformations to apply to the HTML
407 * - allowClone: (bool) Whether to clone the ParserOutput before
408 * applying transformations. Default is true.
409 * - allowTOC: (bool) Show the TOC, assuming there were enough headings
410 * to generate one and `__NOTOC__` wasn't used. Default is true,
411 * but might be statefully overridden.
412 * - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
413 * otherwise the marker will be left in the article (and the skin
414 * will be responsible for replacing or removing it). Default is
415 * true.
416 * - enableSectionEditLinks: (bool) Include section edit links, assuming
417 * section edit link tokens are present in the HTML. Default is true,
418 * but might be statefully overridden.
419 * - userLang: (Language) Language object used for localizing UX messages,
420 * for example the heading of the table of contents. If omitted, will
421 * use the language of the main request context.
422 * - skin: (Skin) Skin object used for transforming section edit links.
423 * - unwrap: (bool) Return text without a wrapper div. Default is false,
424 * meaning a wrapper div will be added if getWrapperDivClass() returns
425 * a non-empty string.
426 * - wrapperDivClass: (string) Wrap the output in a div and apply the given
427 * CSS class to that div. This overrides the output of getWrapperDivClass().
428 * Setting this to an empty string has the same effect as 'unwrap' => true.
429 * - deduplicateStyles: (bool) When true, which is the default, `<style>`
430 * tags with the `data-mw-deduplicate` attribute set are deduplicated by
431 * value of the attribute: all but the first will be replaced by `<link
432 * rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
433 * the scheme-specific-part of the href is the (percent-encoded) value
434 * of the `data-mw-deduplicate` attribute.
435 * - absoluteURLs: (bool) use absolute URLs in all links. Default: false
436 * - includeDebugInfo: (bool) render PP limit report in HTML. Default: false
437 * It is planned to eventually deprecate this $options array and to be able to
438 * pass its content in the $popts ParserOptions.
439 * @return ParserOutput
440 */
441 public function runOutputPipeline( ParserOptions $popts, array $options = [] ): ParserOutput {
442 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
443 $options += [
444 'allowClone' => true,
445 'allowTOC' => true,
446 'injectTOC' => true,
447 'enableSectionEditLinks' => true,
448 'userLang' => null,
449 'skin' => null,
450 'unwrap' => false,
451 'wrapperDivClass' => $this->getWrapperDivClass(),
452 'deduplicateStyles' => true,
453 'absoluteURLs' => false,
454 'includeDebugInfo' => false,
455 ];
456 return $pipeline->run( $this, $popts, $options );
457 }
458
464 public function addCacheMessage( string $msg ): void {
465 $this->mCacheMessage .= $msg;
466 }
467
473 public function addWrapperDivClass( $class ): void {
474 $this->mWrapperDivClasses[$class] = true;
475 }
476
481 public function clearWrapperDivClass(): void {
482 $this->mWrapperDivClasses = [];
483 }
484
490 public function getWrapperDivClass(): string {
491 return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
492 }
493
498 public function setSpeculativeRevIdUsed( $id ): void {
499 $this->mSpeculativeRevId = $id;
500 }
501
506 public function getSpeculativeRevIdUsed(): ?int {
507 return $this->mSpeculativeRevId;
508 }
509
514 public function setSpeculativePageIdUsed( $id ): void {
515 $this->speculativePageIdUsed = $id;
516 }
517
522 public function getSpeculativePageIdUsed() {
523 return $this->speculativePageIdUsed;
524 }
525
530 public function setRevisionTimestampUsed( $timestamp ): void {
531 $this->revisionTimestampUsed = $timestamp;
532 }
533
538 public function getRevisionTimestampUsed() {
539 return $this->revisionTimestampUsed;
540 }
541
546 public function setRevisionUsedSha1Base36( $hash ): void {
547 if ( $hash === null ) {
548 return; // e.g. RevisionRecord::getSha1() returned null
549 }
550
551 if (
552 $this->revisionUsedSha1Base36 !== null &&
553 $this->revisionUsedSha1Base36 !== $hash
554 ) {
555 $this->revisionUsedSha1Base36 = ''; // mismatched
556 } else {
557 $this->revisionUsedSha1Base36 = $hash;
558 }
559 }
560
565 public function getRevisionUsedSha1Base36() {
566 return $this->revisionUsedSha1Base36;
567 }
568
572 private function getLanguageLinksInternal(): array {
573 $result = [];
574 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
575 $result[] = "$lang:$title";
576 }
577 return $result;
578 }
579
587 public function getCategoryNames(): array {
588 # Note that numeric category names get converted to 'int' when
589 # stored as array keys; stringify the keys to ensure they
590 # return to original string form so as not to confuse callers.
591 return array_map( 'strval', array_keys( $this->mCategories ) );
592 }
593
605 public function getCategoryMap(): array {
606 return $this->mCategories;
607 }
608
622 public function getCategorySortKey( string $name ): ?string {
623 // This API avoids exposing the fact that numeric string category
624 // names are going to be converted to 'int' when used as array
625 // keys for the `mCategories` field.
626 return $this->mCategories[$name] ?? null;
627 }
628
633 public function getIndicators(): array {
634 $result = [];
635 foreach ( $this->mIndicatorIds as $id ) {
636 $fragmentName = "indicator:{$id}";
637 $contents = $this->contentHolder->getAsHtmlString( $fragmentName );
638 Assert::invariant( $contents !== null, "fragments should exist" );
639 $result[$id] = $contents;
640 }
641 return $result;
642 }
643
649 public function getTitleText(): string {
650 return $this->mTitleText;
651 }
652
657 public function getTOCData(): ?TOCData {
658 return $this->mTOCData;
659 }
660
665 public function getCacheMessage(): string {
666 return $this->mCacheMessage;
667 }
668
673 public function getSections(): array {
674 if ( $this->mTOCData !== null ) {
675 return $this->mTOCData->toLegacy();
676 }
677 // For compatibility
678 return [];
679 }
680
699 public function getLinkList( string|ParserOutputLinkTypes $linkType, ?int $onlyNamespace = null ): array {
700 if ( is_string( $linkType ) ) {
701 $linkType = ParserOutputLinkTypes::from( $linkType );
702 }
703 # Note that fragments are dropped for everything except language links
704 $result = [];
705 switch ( $linkType ) {
706 case ParserOutputLinkTypes::CATEGORY:
707 if ( $onlyNamespace !== null && $onlyNamespace !== NS_CATEGORY ) {
708 return [];
709 }
710 foreach ( $this->mCategories as $dbkey => $sort ) {
711 $result[] = [
712 'link' => new TitleValue( NS_CATEGORY, (string)$dbkey ),
713 'sort' => $sort,
714 ];
715 }
716 break;
717
718 case ParserOutputLinkTypes::EXISTENCE:
719 $links = $onlyNamespace === null ? $this->existenceLinks : [
720 $onlyNamespace => $this->existenceLinks[$onlyNamespace] ?? [],
721 ];
722 foreach ( $links as $ns => $titles ) {
723 foreach ( $titles as $dbkey => $unused ) {
724 $result[] = [
725 'link' => new TitleValue( $ns, (string)$dbkey )
726 ];
727 }
728 }
729 break;
730
731 case ParserOutputLinkTypes::INTERWIKI:
732 // By convention interwiki links belong to NS_MAIN
733 if ( $onlyNamespace !== null && $onlyNamespace !== NS_MAIN ) {
734 return [];
735 }
736 foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
737 foreach ( $arr as $dbkey => $ignore ) {
738 $result[] = [
739 'link' => new TitleValue( NS_MAIN, (string)$dbkey, '', (string)$prefix ),
740 ];
741 }
742 }
743 break;
744
745 case ParserOutputLinkTypes::LANGUAGE:
746 // By convention language links belong to NS_MAIN
747 if ( $onlyNamespace !== null && $onlyNamespace !== NS_MAIN ) {
748 return [];
749 }
750 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
751 # language links can have fragments!
752 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
753 $result[] = [
754 'link' => new TitleValue( NS_MAIN, $title, $frag, (string)$lang ),
755 ];
756 }
757 break;
758
759 case ParserOutputLinkTypes::LOCAL:
760 $links = $onlyNamespace === null ? $this->mLinks : [
761 $onlyNamespace => $this->mLinks[$onlyNamespace] ?? [],
762 ];
763 foreach ( $links as $ns => $arr ) {
764 foreach ( $arr as $dbkey => $id ) {
765 $result[] = [
766 'link' => new TitleValue( $ns, (string)$dbkey ),
767 'pageid' => $id,
768 ];
769 }
770 }
771 break;
772
773 case ParserOutputLinkTypes::MEDIA:
774 if ( $onlyNamespace !== null && $onlyNamespace !== NS_FILE ) {
775 return [];
776 }
777 foreach ( $this->mImages as $dbkey => $ignore ) {
778 $extra = $this->mFileSearchOptions[$dbkey] ?? [];
779 $extra['link'] = new TitleValue( NS_FILE, (string)$dbkey );
780 $result[] = $extra;
781 }
782 break;
783
784 case ParserOutputLinkTypes::SPECIAL:
785 if ( $onlyNamespace !== null && $onlyNamespace !== NS_SPECIAL ) {
786 return [];
787 }
788 foreach ( $this->mLinksSpecial as $dbkey => $ignore ) {
789 $result[] = [
790 'link' => new TitleValue( NS_SPECIAL, (string)$dbkey ),
791 ];
792 }
793 break;
794
795 case ParserOutputLinkTypes::TEMPLATE:
796 $links = $onlyNamespace === null ? $this->mTemplates : [
797 $onlyNamespace => $this->mTemplates[$onlyNamespace] ?? [],
798 ];
799 foreach ( $links as $ns => $arr ) {
800 foreach ( $arr as $dbkey => $pageid ) {
801 $result[] = [
802 'link' => new TitleValue( $ns, (string)$dbkey ),
803 'pageid' => $pageid,
804 // default to invalid/broken revision if this is not present
805 'revid' => $this->mTemplateIds[$ns][$dbkey] ?? 0,
806 ];
807 }
808 }
809 break;
810
811 default:
812 throw new UnhandledMatchError( "Unknown link type " . $linkType->value );
813 }
814 return $result;
815 }
816
830 public function appendLinkList( string|ParserOutputLinkTypes $linkType, array $linkItem ): void {
831 if ( is_string( $linkType ) ) {
832 $linkType = ParserOutputLinkTypes::from( $linkType );
833 }
834 $link = $linkItem['link'];
835 match ( $linkType ) {
836 ParserOutputLinkTypes::CATEGORY =>
837 $this->addCategory( $link, $linkItem['sort'] ?? '' ),
838 ParserOutputLinkTypes::EXISTENCE =>
839 $this->addExistenceDependency( $link ),
840 ParserOutputLinkTypes::INTERWIKI =>
841 $this->addInterwikiLink( $link ),
842 ParserOutputLinkTypes::LANGUAGE =>
843 $this->addLanguageLink( $link ),
844 ParserOutputLinkTypes::LOCAL =>
845 $this->addLink( $link, $linkItem['pageid'] ?? null ),
846 ParserOutputLinkTypes::MEDIA =>
847 $this->addImage( $link, $linkItem['time'] ?? null, $linkItem['sha1'] ?? null ),
848 ParserOutputLinkTypes::SPECIAL =>
849 $this->addLink( $link ),
850 ParserOutputLinkTypes::TEMPLATE =>
851 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
852 $this->addTemplate( $link, $linkItem['pageid'], $linkItem['revid'] ),
853 };
854 }
855
862 public function hasLinks(): bool {
863 foreach ( $this->mLinks as $ns => $arr ) {
864 foreach ( $arr as $dbkey => $id ) {
865 return true;
866 }
867 }
868 return false;
869 }
870
876 public function hasImages(): bool {
877 return $this->mImages !== [];
878 }
879
885 public function getExternalLinks(): array {
886 return $this->mExternalLinks;
887 }
888
893 public function setNoGallery( $value ): void {
894 $this->setOutputFlag( ParserOutputFlags::NO_GALLERY, (bool)$value );
895 }
896
901 public function getNoGallery() {
902 return $this->getOutputFlag( ParserOutputFlags::NO_GALLERY );
903 }
904
908 public function getHeadItems() {
909 return $this->mHeadItems;
910 }
911
915 public function getModules() {
916 return array_keys( $this->mModuleSet );
917 }
918
922 public function getModuleStyles() {
923 return array_keys( $this->mModuleStyleSet );
924 }
925
933 public function getJsConfigVars( bool $showStrategyKeys = false ) {
934 $result = $this->mJsConfigVars;
935 // Don't expose the internal strategy key
936 foreach ( $result as &$value ) {
937 if ( is_array( $value ) && !$showStrategyKeys ) {
938 if ( ( $value[self::MW_MERGE_STRATEGY_KEY] ?? null ) === MergeStrategy::SUM->value ) {
939 $value = $value['value'];
940 continue;
941 }
942 unset( $value[self::MW_MERGE_STRATEGY_KEY] );
943 }
944 }
945 return $result;
946 }
947
949 public function getWarnings(): array {
950 // T343048: Don't emit deprecation warnings here until the
951 // compatibility fallback in ApiParse is removed.
952 return array_keys( $this->mWarnings );
953 }
954
956 public function getWarningMsgs(): array {
957 return array_values( $this->mWarningMsgs );
958 }
959
960 public function getIndexPolicy(): string {
961 // 'noindex' wins if both are set. (T16899)
962 if ( $this->getOutputFlag( ParserOutputFlags::NO_INDEX_POLICY ) ) {
963 return 'noindex';
964 } elseif ( $this->getOutputFlag( ParserOutputFlags::INDEX_POLICY ) ) {
965 return 'index';
966 }
967 return '';
968 }
969
973 public function getRevisionTimestamp(): ?string {
974 return $this->mTimestamp;
975 }
976
980 public function getLimitReportData() {
981 return $this->mLimitReportData;
982 }
983
987 public function getLimitReportJSData() {
988 return $this->mLimitReportJSData;
989 }
990
995 public function getEnableOOUI() {
996 return $this->getOutputFlag( ParserOutputFlags::ENABLE_OOUI );
997 }
998
1004 public function getExtraCSPDefaultSrcs() {
1005 return $this->mExtraDefaultSrcs;
1006 }
1007
1013 public function getExtraCSPScriptSrcs() {
1014 return $this->mExtraScriptSrcs;
1015 }
1016
1022 public function getExtraCSPStyleSrcs() {
1023 return $this->mExtraStyleSrcs;
1024 }
1025
1027 public function clearLanguageLinks(): void {
1028 $this->mLanguageLinkMap = [];
1029 }
1030
1036 public function setTitleText( string $t ) {
1037 $this->mDisplayTitleParts = null;
1038 return wfSetVar( $this->mTitleText, $t );
1039 }
1040
1044 public function setTOCData( TOCData $tocData ): void {
1045 $this->mTOCData = $tocData;
1046 }
1047
1052 public function setSections( array $sectionArray ) {
1053 $oldValue = $this->getSections();
1054 $this->setTOCData( TOCData::fromLegacy( $sectionArray ) );
1055 return $oldValue;
1056 }
1057
1071 public function setIndexPolicy( $policy ): string {
1072 $old = $this->getIndexPolicy();
1073 if ( $policy === 'noindex' ) {
1074 $this->setOutputFlag( ParserOutputFlags::NO_INDEX_POLICY );
1075 } elseif ( $policy === 'index' ) {
1076 $this->setOutputFlag( ParserOutputFlags::INDEX_POLICY );
1077 }
1078 return $old;
1079 }
1080
1084 public function setRevisionTimestamp( ?string $timestamp ): void {
1085 $this->mTimestamp = $timestamp;
1086 }
1087
1103 public function addCategory( $c, $sort = '' ): void {
1104 if ( $c instanceof ParsoidLinkTarget ) {
1105 $c = $c->getDBkey();
1106 }
1107 if ( ( $this->mCategories[$c] ?? $sort ) !== $sort ) {
1108 // Overwriting a category sort key prevents selective update
1109 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1110 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1111 }
1112 $this->mCategories[$c] = $sort;
1113 }
1114
1120 public function setCategories( array $c ): void {
1121 if ( ( $this->mCategories ?: $c ) !== $c ) {
1122 // Overwriting categories prevents selective update
1123 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1124 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1125 }
1126 $this->mCategories = $c;
1127 }
1128
1135 public function setIndicator( $id, $content ): void {
1136 Assert::parameterType( 'string', $content, 'content' );
1137 $fragmentName = "indicator:{$id}";
1138 if ( $this->contentHolder->has( $fragmentName ) ) {
1139 // Overwriting an indicator prevents selective update
1140 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1141 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1142 } else {
1143 $this->mIndicatorIds[] = $id;
1144 }
1145 $this->getContentHolder()->setAsHtmlString( $fragmentName, $content );
1146 }
1147
1151 public function setIndicatorDom( string $id, DocumentFragment $content ): void {
1152 $fragmentName = "indicator:{$id}";
1153 if ( $this->contentHolder->has( $fragmentName ) ) {
1154 // Overwriting an indicator prevents selective update
1155 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1156 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1157 } else {
1158 $this->mIndicatorIds[] = $id;
1159 }
1160 $this->getContentHolder()->setAsDom( $fragmentName, $content );
1161 }
1162
1171 public function setEnableOOUI( bool $enable = false ): void {
1172 $this->setOutputFlag( ParserOutputFlags::ENABLE_OOUI, $enable );
1173 }
1174
1179 public function addLanguageLink( $t ): void {
1180 # Note that fragments are preserved
1181 if ( $t instanceof ParsoidLinkTarget ) {
1182 // Language links are unusual in using 'text' rather than 'db key'
1183 // Note that fragments are preserved.
1184 $lang = $t->getInterwiki();
1185 $title = $t->getText();
1186 if ( $t->hasFragment() ) {
1187 $title .= '#' . $t->getFragment();
1188 }
1189 } else {
1190 [ $lang, $title ] = array_pad( explode( ':', $t, 2 ), -2, '' );
1191 }
1192 if ( $lang === '' ) {
1193 throw new InvalidArgumentException( __METHOD__ . ' without prefix' );
1194 }
1195 if ( ( $this->mLanguageLinkMap[$lang] ?? $title ) !== $title ) {
1196 // Overwriting a language link prevents selective update
1197 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1198 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1199 }
1200 $this->mLanguageLinkMap[$lang] ??= $title;
1201 }
1202
1211 public function addWarningMsgVal( MessageSpecifier $mv, ?string $key = null ) {
1212 $mv = MessageValue::newFromSpecifier( $mv );
1213 $key ??= $mv->getKey();
1214 if ( array_key_exists( $key, $this->mWarningMsgs ) ) {
1215 // Overwriting a warning message prevents selective update
1216 // [[mw:Parsoid/Internals/Handling_resource_limits]]
1217 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
1218 }
1219 $this->mWarningMsgs[$key] = $mv;
1220 // Ensure callers aren't passing nonserializable arguments: T343048.
1221 $jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
1222 $path = $jsonCodec->detectNonSerializableData( $mv, true );
1223 if ( $path !== null ) {
1224 throw new InvalidArgumentException( __METHOD__ . ": nonserializable" );
1225 }
1226 // For backward compatibility with callers of ::getWarnings()
1227 // and rollback compatibility for ParserCache; don't remove
1228 // until we no longer need rollback compatibility with MW 1.43.
1229 $s = Message::newFromSpecifier( $mv )
1230 // some callers set the title here?
1231 ->inContentLanguage() // because this ends up in cache
1232 ->text();
1233 $this->mWarnings[$s] = 1;
1234 }
1235
1244 public function addWarningMsg( string $msg, ...$args ): void {
1245 // T227447: Once MessageSpecifier is moved to a library, Parsoid would
1246 // be able to use ::addWarningMsgVal() directly and this method
1247 // could be deprecated and removed.
1248 $this->addWarningMsgVal( MessageValue::new( $msg, $args ) );
1249 }
1250
1255 public function setNewSection( $value ): void {
1256 $this->setOutputFlag( ParserOutputFlags::NEW_SECTION, (bool)$value );
1257 }
1258
1263 public function setHideNewSection( bool $value ): void {
1264 $this->setOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION, $value );
1265 }
1266
1270 public function getHideNewSection(): bool {
1271 return $this->getOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION );
1272 }
1273
1277 public function getNewSection(): bool {
1278 return $this->getOutputFlag( ParserOutputFlags::NEW_SECTION );
1279 }
1280
1289 public static function isLinkInternal( $internal, $url ): bool {
1290 return (bool)preg_match( '/^' .
1291 # If server is proto relative, check also for http/https links
1292 ( str_starts_with( $internal, '//' ) ? '(?:https?:)?' : '' ) .
1293 preg_quote( $internal, '/' ) .
1294 # check for query/path/anchor or end of link in each case
1295 '(?:[\?\/\#]|$)/i',
1296 $url
1297 );
1298 }
1299
1303 public function addExternalLink( $url ): void {
1304 # We don't register links pointing to our own server, unless... :-)
1305 $config = MediaWikiServices::getInstance()->getMainConfig();
1306 $server = $config->get( MainConfigNames::Server );
1307 $registerInternalExternals = $config->get( MainConfigNames::RegisterInternalExternals );
1308 $ignoreDomains = $config->get( MainConfigNames::ExternalLinksIgnoreDomains );
1309
1310 # Replace unnecessary URL escape codes with the referenced character
1311 # This prevents spammers from hiding links from the filters
1312 $url = Parser::normalizeLinkUrl( $url );
1313
1314 $registerExternalLink = true;
1315 if ( !$registerInternalExternals ) {
1316 $registerExternalLink = !self::isLinkInternal( $server, $url );
1317 }
1318 if (
1319 MediaWikiServices::getInstance()->getUrlUtils()->matchesDomainList( $url, $ignoreDomains )
1320 ) {
1321 $registerExternalLink = false;
1322 }
1323 if ( $registerExternalLink ) {
1324 $this->mExternalLinks[$url] = 1;
1325 }
1326 }
1327
1334 public function addLink( ParsoidLinkTarget $link, $id = null ): void {
1335 if ( $link->isExternal() ) {
1336 // Don't record interwikis in pagelinks
1337 $this->addInterwikiLink( $link );
1338 return;
1339 }
1340 $ns = $link->getNamespace();
1341 $dbk = $link->getDBkey();
1342 if ( $ns === NS_MEDIA ) {
1343 // Normalize this pseudo-alias if it makes it down here...
1344 $ns = NS_FILE;
1345 } elseif ( $ns === NS_SPECIAL ) {
1346 // We don't want to record Special: links in the database, so put them in a separate place.
1347 // It might actually be wise to, but we'd need to do some normalization.
1348 $this->mLinksSpecial[$dbk] = 1;
1349 return;
1350 } elseif ( $dbk === '' ) {
1351 // Don't record self links - [[#Foo]]
1352 return;
1353 }
1354 if ( $id === null ) {
1355 // T357048: This actually kills performance; we should batch these.
1356 $page = MediaWikiServices::getInstance()->getPageStore()->getPageForLink( $link );
1357 $id = $page->getId();
1358 }
1359 $this->mLinks[$ns][$dbk] = $id;
1360 }
1361
1368 public function addImage( $name, $timestamp = null, $sha1 = null ): void {
1369 if ( $name instanceof ParsoidLinkTarget ) {
1370 $name = $name->getDBkey();
1371 }
1372 $this->mImages[$name] = 1;
1373 if ( $timestamp !== null && $sha1 !== null ) {
1374 $this->mFileSearchOptions[$name] = [ 'time' => $timestamp, 'sha1' => $sha1 ];
1375 }
1376 }
1377
1385 public function addTemplate( $link, $page_id, $rev_id ): void {
1386 if ( $link->isExternal() ) {
1387 // Will throw an InvalidArgumentException in a future release.
1388 throw new InvalidArgumentException( __METHOD__ . " with interwiki link" );
1389 }
1390 $ns = $link->getNamespace();
1391 $dbk = $link->getDBkey();
1392 // T357048: Parsoid doesn't have page_id
1393 $this->mTemplates[$ns][$dbk] = $page_id;
1394 $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
1395 }
1396
1401 public function addInterwikiLink( $link ): void {
1402 if ( !$link->isExternal() ) {
1403 throw new InvalidArgumentException( 'Non-interwiki link passed, internal parser error.' );
1404 }
1405 $prefix = $link->getInterwiki();
1406 $this->mInterwikiLinks[$prefix][$link->getDBkey()] = 1;
1407 }
1408
1416 public function addExistenceDependency( ParsoidLinkTarget $link ) {
1417 $ns = $link->getNamespace();
1418 $dbk = $link->getDBkey();
1419 // Ignore some kinds of links, as in addLink()
1420 if ( $link->isExternal() || $ns === NS_SPECIAL || $dbk === '' ) {
1421 return;
1422 }
1423 if ( $ns === NS_MEDIA ) {
1424 $ns = NS_FILE;
1425 }
1426 $this->existenceLinks[$ns][$dbk] = true;
1427 }
1428
1436 public function addHeadItem( $section, $tag = false ): void {
1437 if ( $tag !== false ) {
1438 $this->mHeadItems[$tag] = $section;
1439 } else {
1440 $this->mHeadItems[] = $section;
1441 }
1442 }
1443
1448 public function addModules( array $modules ): void {
1449 $modules = array_fill_keys( $modules, true );
1450 $this->mModuleSet = array_merge( $this->mModuleSet, $modules );
1451 }
1452
1457 public function addModuleStyles( array $modules ): void {
1458 $modules = array_fill_keys( $modules, true );
1459 $this->mModuleStyleSet = array_merge( $this->mModuleStyleSet, $modules );
1460 }
1461
1472 public function addJsConfigVars( $keys, $value = null ): void {
1473 wfDeprecated( __METHOD__, '1.38' );
1474 if ( is_array( $keys ) ) {
1475 foreach ( $keys as $key => $value ) {
1476 $this->mJsConfigVars[$key] = $value;
1477 }
1478 return;
1479 }
1480
1481 $this->mJsConfigVars[$keys] = $value;
1482 }
1483
1497 public function setJsConfigVar( string $key, $value ): void {
1498 if (
1499 array_key_exists( $key, $this->mJsConfigVars ) &&
1500 $this->mJsConfigVars[$key] !== $value
1501 ) {
1502 // Ensure that a key is mapped to only a single value in order
1503 // to prevent the resulting array from varying if content
1504 // is parsed in a different order.
1505 throw new InvalidArgumentException( "Multiple conflicting values given for $key" );
1506 }
1507 $this->mJsConfigVars[$key] = $value;
1508 }
1509
1526 public function appendJsConfigVar(
1527 string $key,
1528 $value,
1529 MergeStrategy|string $strategy = MergeStrategy::UNION
1530 ): void {
1531 if ( is_string( $strategy ) ) {
1532 $strategy = MergeStrategy::from( $strategy );
1533 }
1534 $this->mJsConfigVars = self::mergeMapStrategy(
1535 $this->mJsConfigVars,
1536 [ $key => self::makeMapStrategy( $value, $strategy ) ]
1537 );
1538 }
1539
1555 public function addOutputPageMetadata( OutputPage $out ): void {
1556 // This should eventually use the same merge mechanism used
1557 // internally to merge ParserOutputs together.
1558 // (ie: $this->mergeHtmlMetaDataFrom( $out->getMetadata() )
1559 // once preventClickjacking, moduleStyles, modules, jsconfigvars,
1560 // and head items are moved to OutputPage::$metadata)
1561
1562 // Take the strictest click-jacking policy. This is to ensure any one-click features
1563 // such as patrol or rollback on the transcluded special page will result in the wiki page
1564 // disallowing embedding in cross-origin iframes. Articles are generally allowed to be
1565 // embedded. Pages that transclude special pages are expected to be user pages or
1566 // other non-content pages that content re-users won't discover or care about.
1567 $this->setOutputFlag(
1568 ParserOutputFlags::PREVENT_CLICKJACKING,
1569 $this->getOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING ) ||
1570 $out->getOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING )
1571 );
1572
1573 $this->addModuleStyles( $out->getModuleStyles() );
1574
1575 // TODO: Figure out if style modules suffice, or whether the below is needed as well.
1576 // Are there special pages that permit transcluding/including and also have JS modules
1577 // that should be activate on the host page?
1578 $this->addModules( $out->getModules() );
1579 $this->mJsConfigVars = self::mergeMapStrategy(
1580 $this->mJsConfigVars, $out->getJsConfigVars()
1581 );
1582 $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
1583 }
1584
1598 public function setDisplayTitle( string $text ): void {
1599 $this->setTitleText( $text );
1600 $this->setPageProperty( 'displaytitle', $text );
1601 }
1602
1625 public function setDisplayTitleParts(
1626 string|HtmlArmor $nsText,
1627 string|HtmlArmor $nsSeparator,
1628 string|HtmlArmor $mainText,
1629 string|HtmlArmor|null $combinedText = null
1630 ): void {
1631 $parts = [
1632 HtmlArmor::getHtml( $nsText ) ?? '',
1633 HtmlArmor::getHtml( $nsSeparator ) ?? '',
1634 HtmlArmor::getHtml( $mainText ) ?? '',
1635 ];
1636 if ( $combinedText !== null ) {
1637 $combinedText = HtmlArmor::getHtml( $combinedText );
1638 }
1639 $combinedText ??= $parts[0] ? implode( '', $parts ) : $parts[2];
1640 $this->mDisplayTitleParts = $parts;
1641 $this->mTitleText = $combinedText;
1642 }
1643
1655 string $nsTextHtml,
1656 string $nsSeparatorHtml,
1657 string $mainTextHtml,
1658 ?string $combinedTextHtml = null
1659 ): void {
1660 $this->setDisplayTitleParts(
1661 new HtmlArmor( $nsTextHtml ),
1662 new HtmlArmor( $nsSeparatorHtml ),
1663 new HtmlArmor( $mainTextHtml ),
1664 // HtmlArmor can wrap `null`
1665 new HtmlArmor( $combinedTextHtml ),
1666 );
1667 }
1668
1680 public function getDisplayTitleParts(): ?array {
1681 if ( $this->mDisplayTitleParts === null ) {
1682 return null;
1683 }
1684 return [
1685 new HtmlArmor( $this->mDisplayTitleParts[0] ),
1686 new HtmlArmor( $this->mDisplayTitleParts[1] ),
1687 new HtmlArmor( $this->mDisplayTitleParts[2] ),
1688 ];
1689 }
1690
1700 public function getDisplayTitle(): string|false {
1701 $t = $this->getTitleText();
1702 if ( $t === '' ) {
1703 return false;
1704 }
1705 return $t;
1706 }
1707
1716 public function getTitle(): ?ParsoidLinkTarget {
1717 $ns = $this->getExtensionData( 'core:title-ns' );
1718 $dbkey = $this->getExtensionData( 'core:title-dbkey' );
1719 if ( $dbkey !== null ) {
1720 return new TitleValue( $ns ?? NS_MAIN, $dbkey );
1721 }
1722 // Backward-compatibility with cache contents generated by MW < 1.46
1723 $dbkey = $this->getExtensionData( 'parsoid:title-dbkey' );
1724 if ( $dbkey !== null ) {
1725 // This is a prefixed DB key w/ localized namespace
1726 $titleFactory = MediaWikiServices::getInstance()->getTitleFactory();
1727 return $titleFactory->newFromDBkey( $dbkey );
1728 }
1729 return null;
1730 }
1731
1742 public function setTitle( ParsoidLinkTarget|PageReference $title ): void {
1743 if ( $title instanceof PageReference ) {
1744 $title->assertWiki( WikiAwareEntity::LOCAL );
1745 } else {
1746 Assert::invariant( !$title->isExternal(), "title should be local" );
1747 }
1748 $this->setExtensionData( 'core:title-ns', $title->getNamespace() );
1749 $this->setExtensionData( 'core:title-dbkey', $title->getDBkey() );
1750 }
1751
1790 public function getLanguage(): ?Bcp47Code {
1791 // This information is temporarily stored in extension data (T303329)
1792 $code = $this->getExtensionData( 'core:target-lang-variant' );
1793 // This is null if the ParserOutput was cached by MW 1.40 or earlier,
1794 // or not constructed by Parser/ParserCache.
1795 return $code === null ? null : new Bcp47CodeValue( $code );
1796 }
1797
1807 public function setLanguage( Bcp47Code $lang ): void {
1808 $this->setExtensionData( 'core:target-lang-variant', $lang->toBcp47Code() );
1809 }
1810
1817 public function getRedirectHeader(): ?string {
1818 return $this->getExtensionData( 'core:redirect-header' );
1819 }
1820
1825 public function setRedirectHeader( string $html ): void {
1826 $this->setExtensionData( 'core:redirect-header', $html );
1827 }
1828
1837 public function setRenderId( string $renderId ): void {
1838 $this->setExtensionData( 'core:render-id', $renderId );
1839 }
1840
1848 public function getRenderId(): ?string {
1849 // Backward-compatibility with old cache contents
1850 // Can be removed after parser cache contents have expired
1851 $old = $this->getExtensionData( 'parsoid-render-id' );
1852 if ( $old !== null ) {
1853 return ParsoidRenderId::newFromKey( $old )->getUniqueID();
1854 }
1855 return $this->getExtensionData( 'core:render-id' );
1856 }
1857
1863 public function getAllFlags(): array {
1864 // Before MW 1.46 this did not include NO_GALLERY, ENABLE_OOUI,
1865 // INDEX_POLICY, NO_INDEX_POLICY, NEW_SECTION, HIDE_NEW_SECTION,
1866 // and PREVENT_CLICKJACKING, but this method is only used internally.
1867 // See WikitextContentHandler::preSaveTransform() where this method
1868 // is used to transfer PST flags to the WikitextContent object, and
1869 // OutputPage::addParserOutputMetadata() where this method is used
1870 // to transfer ParserOutput flags to OutputPage::$mOutputFlags
1871 return array_keys( $this->mFlags );
1872 }
1873
1966 public function setPageProperty( string $name, string $value ): void {
1967 $this->setUnsortedPageProperty( $name, $value );
1968 }
1969
1984 public function setNumericPageProperty( string $propName, $numericValue ): void {
1985 if ( !is_numeric( $numericValue ) ) {
1986 throw new InvalidArgumentException( __METHOD__ . " with non-numeric value" );
1987 }
1988 // Coerce numeric sort key to a number.
1989 $this->mProperties[$propName] = 0 + $numericValue;
1990 }
1991
2009 public function setUnsortedPageProperty( string $propName, string $value = '' ): void {
2010 $this->mProperties[$propName] = $value;
2011 }
2012
2026 public function getPageProperty( string $name ) {
2027 return $this->mProperties[$name] ?? null;
2028 }
2029
2035 public function unsetPageProperty( string $name ): void {
2036 unset( $this->mProperties[$name] );
2037 }
2038
2044 public function getPageProperties(): array {
2045 return $this->mProperties;
2046 }
2047
2070 public function setOutputFlag( ParserOutputFlags|string $name, bool $val = true ): void {
2071 if ( is_string( $name ) ) {
2072 $flag = ParserOutputFlags::tryFrom( $name );
2073 if ( $flag === null ) {
2075 __METHOD__ . ' with non-standard flag', '1.45'
2076 );
2077 }
2078 } else {
2079 $flag = $name;
2080 $name = $flag->value;
2081 }
2082 if ( $val ) {
2083 $this->mFlags[$name] = true;
2084 } else {
2085 unset( $this->mFlags[$name] );
2086 }
2087 }
2088
2103 public function getOutputFlag( ParserOutputFlags|string $flag ): bool {
2104 // In the future we will return false if $flag doesn't correspond to a
2105 // valid ParserOutputFlag; see deprecation notice in ::setOutputFlag().
2106 $name = $flag instanceof ParserOutputFlags ? $flag->value : $flag;
2107 return $this->mFlags[$name] ?? false;
2108 }
2109
2121 public function appendOutputStrings( string|ParserOutputStringSets $name, array $value ): void {
2122 if ( is_string( $name ) ) {
2123 $name = ParserOutputStringSets::from( $name );
2124 }
2125 match ( $name ) {
2126 ParserOutputStringSets::MODULE =>
2127 $this->addModules( $value ),
2128 ParserOutputStringSets::MODULE_STYLE =>
2129 $this->addModuleStyles( $value ),
2130 ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC =>
2131 array_walk( $value, fn ( $v, $i ) =>
2132 $this->addExtraCSPDefaultSrc( $v )
2133 ),
2134 ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC =>
2135 array_walk( $value, fn ( $v, $i ) =>
2136 $this->addExtraCSPScriptSrc( $v )
2137 ),
2138 ParserOutputStringSets::EXTRA_CSP_STYLE_SRC =>
2139 array_walk( $value, fn ( $v, $i ) =>
2140 $this->addExtraCSPStyleSrc( $v )
2141 ),
2142 };
2143 }
2144
2157 public function getOutputStrings( string|ParserOutputStringSets $name ): array {
2158 if ( is_string( $name ) ) {
2159 $name = ParserOutputStringSets::from( $name );
2160 }
2161 return match ( $name ) {
2162 ParserOutputStringSets::MODULE =>
2163 $this->getModules(),
2164 ParserOutputStringSets::MODULE_STYLE =>
2165 $this->getModuleStyles(),
2166 ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC =>
2167 $this->getExtraCSPDefaultSrcs(),
2168 ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC =>
2169 $this->getExtraCSPScriptSrcs(),
2170 ParserOutputStringSets::EXTRA_CSP_STYLE_SRC =>
2171 $this->getExtraCSPStyleSrcs(),
2172 };
2173 }
2174
2224 public function setExtensionData( $key, $value ): void {
2225 if (
2226 array_key_exists( $key, $this->mExtensionData ) &&
2227 $this->mExtensionData[$key] !== $value
2228 ) {
2229 // This is discouraged, as it prevents selective update,
2230 // and was deprecated in 1.38.
2231 $this->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
2232 }
2233 if ( $value === null ) {
2234 unset( $this->mExtensionData[$key] );
2235 } else {
2236 $this->mExtensionData[$key] = $value;
2237 }
2238 }
2239
2264 public function appendExtensionData(
2265 string $key,
2266 $value,
2267 MergeStrategy|string $strategy = MergeStrategy::UNION
2268 ): void {
2269 if ( is_string( $strategy ) ) {
2270 $strategy = MergeStrategy::from( $strategy );
2271 }
2272 $this->mExtensionData = self::mergeMapStrategy(
2273 $this->mExtensionData,
2274 [ $key => self::makeMapStrategy( $value, $strategy ) ]
2275 );
2276 }
2277
2289 public function getExtensionData( $key ) {
2290 $value = $this->mExtensionData[$key] ?? null;
2291 if ( is_array( $value ) ) {
2292 if ( ( $value[self::MW_MERGE_STRATEGY_KEY] ?? null ) === MergeStrategy::SUM->value ) {
2293 return $value['value'];
2294 }
2295 // Don't expose our internal merge strategy key.
2296 unset( $value[self::MW_MERGE_STRATEGY_KEY] );
2297 }
2298 return $value;
2299 }
2300
2301 private static function getTimes( ?string $clock = null ): array {
2302 $ret = [];
2303 if ( !$clock || $clock === 'wall' ) {
2304 $ret['wall'] = hrtime( true ) / 10 ** 9;
2305 }
2306 if ( !$clock || $clock === 'cpu' ) {
2307 $ru = getrusage( 0 /* RUSAGE_SELF */ );
2308 $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
2309 $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
2310 }
2311 return $ret;
2312 }
2313
2320 public function resetParseStartTime(): void {
2321 $this->mParseStartTime = self::getTimes();
2322 $this->mTimeProfile = [];
2323 }
2324
2334 public function clearParseStartTime(): void {
2335 $this->mParseStartTime = [];
2336 }
2337
2348 public function recordTimeProfile() {
2349 if ( !$this->mParseStartTime ) {
2350 // If resetParseStartTime was never called, there is nothing to record
2351 return;
2352 }
2353
2354 if ( $this->mTimeProfile !== [] ) {
2355 // Don't override the times recorded by the previous call to recordTimeProfile().
2356 return;
2357 }
2358
2359 $now = self::getTimes();
2360 $this->mTimeProfile = [
2361 'wall' => $now['wall'] - $this->mParseStartTime['wall'],
2362 'cpu' => $now['cpu'] - $this->mParseStartTime['cpu'],
2363 ];
2364 }
2365
2383 public function getTimeProfile( string $clock ) {
2384 return $this->mTimeProfile[ $clock ] ?? null;
2385 }
2386
2406 public function setLimitReportData( $key, $value ): void {
2407 $this->mLimitReportData[$key] = $value;
2408
2409 if ( is_array( $value ) ) {
2410 if ( array_keys( $value ) === [ 0, 1 ]
2411 && is_numeric( $value[0] )
2412 && is_numeric( $value[1] )
2413 ) {
2414 $data = [ 'value' => $value[0], 'limit' => $value[1] ];
2415 } else {
2416 $data = $value;
2417 }
2418 } else {
2419 $data = $value;
2420 }
2421
2422 if ( strpos( $key, '-' ) ) {
2423 [ $ns, $name ] = explode( '-', $key, 2 );
2424 $this->mLimitReportJSData[$ns][$name] = $data;
2425 } else {
2426 $this->mLimitReportJSData[$key] = $data;
2427 }
2428 }
2429
2446 public function hasReducedExpiry(): bool {
2447 if ( $this->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) ) {
2448 // If this page has async content, then we should re-run
2449 // RefreshLinksJob whenever we regenerate the page.
2450 return true;
2451 }
2452 $parserCacheExpireTime = MediaWikiServices::getInstance()->getMainConfig()->get(
2453 MainConfigNames::ParserCacheExpireTime );
2454
2455 // Deliberately not using ::getCacheExpiry() here, which can get
2456 // quantized in MiserMode.
2457 return ( $this->mCacheExpiry ?? $parserCacheExpireTime ) < $parserCacheExpireTime;
2458 }
2459
2461 public function getCacheExpiry(): int {
2462 $expiry = parent::getCacheExpiry();
2463 if ( $expiry <= 0 ) {
2464 // Uncacheable.
2465 // Note that this function should return 0 if and only if
2466 // ::isCacheable() returns false.
2467 return 0;
2468 }
2469 // Raise the minimum to ~24 hrs for content pages on large
2470 // wiki farms when MiserMode is enabled. (T416616)
2471 // Implemented as expiring around the next midnight. We take
2472 // both UTC midnight and midnight in a wiki-configured
2473 // timezone into account. On English-language and
2474 // multilingual wikis we get 24 hours (given UTC is the
2475 // timezone), and other wikis split as 1h/23h or down to
2476 // 12h/12h.
2477 $services = MediaWikiServices::getInstance();
2478 $config = $services->getMainConfig();
2479 if (
2480 $config->get( MainConfigNames::MiserMode ) &&
2481 $services->getNamespaceInfo()->isContent(
2482 $this->getTitle()?->getNamespace() ?? NS_MAIN
2483 )
2484 ) {
2485 $date = DateTimeImmutable::createFromInterface(
2486 MWTimestamp::fromMW( $this->getCacheTime() )->timestamp
2487 );
2488 // T419439: the deadline, skew, and stagger here should probably
2489 // be configurable.
2490 $utcMidnight = $date
2491 ->modify( 'next day midnight' )
2492 ->getTimestamp();
2493 $localMidnight = $date
2494 ->setTimeZone( new DateTimeZone(
2495 $config->get( MainConfigNames::Localtimezone )
2496 ) )
2497 ->modify( 'next day midnight' )
2498 ->getTimestamp();
2499 // Whichever comes first: UTC midnight, local midnight, or expiry
2500 $timeToNextMidnight = min( $utcMidnight, $localMidnight ) - $date->getTimestamp();
2501 // "Randomly" stagger expiry across a window to avoid cache
2502 // stampedes.
2503 $stagger = 60 * 60; // 1 hour
2504 if ( $timeToNextMidnight < ( $stagger / 2 ) ) {
2505 // Account for clock skew and unlucky parses just before
2506 // midnight by ensuring our "midnight" deadline is at least
2507 // half-a-stagger away (but halve the stagger in that case
2508 // so we never spread the expiry past midnight+stagger).
2509 $stagger /= 2;
2510 $timeToNextMidnight = $stagger;
2511 }
2512 // Stagger is a function of parse time, which should be random-ish.
2513 $timeToNextMidnight += ( $date->getTimestamp() % $stagger );
2514 $expiry = max( $expiry, $timeToNextMidnight );
2515 }
2516
2517 if ( $this->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY ) ) {
2518 $asyncExpireTime = $config->get(
2519 MainConfigNames::ParserCacheAsyncExpireTime
2520 );
2521 $expiry = max( 1, min( $expiry, $asyncExpireTime ) );
2522 }
2523 return $expiry;
2524 }
2525
2539 public function setPreventClickjacking( bool $flag ): void {
2540 $this->setOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING, $flag );
2541 }
2542
2551 public function getPreventClickjacking(): bool {
2552 return $this->getOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING );
2553 }
2554
2564 public function updateRuntimeAdaptiveExpiry( int $ttl, ?string $source = null ): void {
2565 $this->mMaxAdaptiveExpiry ??= $ttl;
2566 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
2567 $this->updateCacheExpiry( $ttl, $source );
2568 }
2569
2579 public function addExtraCSPDefaultSrc( $src ): void {
2580 $this->mExtraDefaultSrcs[] = $src;
2581 }
2582
2589 public function addExtraCSPStyleSrc( $src ): void {
2590 $this->mExtraStyleSrcs[] = $src;
2591 }
2592
2601 public function addExtraCSPScriptSrc( $src ): void {
2602 $this->mExtraScriptSrcs[] = $src;
2603 }
2604
2610 public function finalizeAdaptiveCacheExpiry(): void {
2611 if ( $this->mMaxAdaptiveExpiry === null ) {
2612 return; // not set
2613 }
2614
2615 $runtime = $this->getTimeProfile( 'wall' );
2616 if ( is_float( $runtime ) ) {
2617 $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
2618 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
2619 // SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
2620 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
2621
2622 $adaptiveTTL = intval( $slope * $runtime + $point );
2623 $adaptiveTTL = max( $adaptiveTTL, self::MIN_AR_TTL );
2624 $adaptiveTTL = min( $adaptiveTTL, $this->mMaxAdaptiveExpiry );
2625 $this->updateCacheExpiry( $adaptiveTTL, 'adaptive-ttl' );
2626 }
2627 }
2628
2633 public function setFromParserOptions( ParserOptions $parserOptions ) {
2634 // Copied from Parser.php::parse and should probably be abstracted
2635 // into the parent base class (probably as part of T236809)
2636 // Wrap non-interface parser output in a <div> so it can be targeted
2637 // with CSS (T37247)
2638 $class = $parserOptions->getWrapOutputClass();
2639 if ( $class !== false && !$parserOptions->isMessage() ) {
2640 $this->addWrapperDivClass( $class );
2641 }
2642
2643 // Record whether this is a preview parse in the output (T341010)
2644 if ( $parserOptions->getIsPreview() ) {
2645 $this->setOutputFlag( ParserOutputFlags::IS_PREVIEW, true );
2646 // Ensure that previews aren't cacheable, just to be safe.
2647 $this->updateCacheExpiry( 0, 'preview' );
2648 }
2649
2650 // Record whether this was parsed with the legacy parser
2651 // (Unlike some other options here, this does/should fork the cache.)
2652 if ( $parserOptions->getUseParsoid() ) {
2653 $this->setOutputFlag( ParserOutputFlags::USE_PARSOID, true );
2654 }
2655 }
2656
2664 $this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
2665 $this->mWarningMsgs = self::mergeMap( $this->mWarningMsgs, $source->mWarningMsgs );
2666 $this->mTimestamp = self::useMaxValue( $this->mTimestamp, $source->getRevisionTimestamp() );
2667 if ( $source->hasCacheTime() ) {
2668 $sourceCacheTime = $source->getCacheTime();
2669 if (
2670 !$this->hasCacheTime() ||
2671 // "undocumented use of -1 to mean not cacheable"
2672 // deprecated, but still supported by ::setCacheTime()
2673 strval( $sourceCacheTime ) === '-1' ||
2674 (
2675 strval( $this->getCacheTime() ) !== '-1' &&
2676 // use newer of the two times
2677 $this->getCacheTime() < $sourceCacheTime
2678 )
2679 ) {
2680 $this->setCacheTime( $sourceCacheTime );
2681 }
2682 }
2683 if ( $source->getRenderId() !== null ) {
2684 // Final render ID should be a function of all component POs
2685 $rid = ( $this->getRenderId() ?? '' ) . $source->getRenderId();
2686 $this->setRenderId( $rid );
2687 }
2688 if ( $source->getCacheRevisionId() !== null ) {
2689 $sourceCacheRevisionId = $source->getCacheRevisionId();
2690 $thisCacheRevisionId = $this->getCacheRevisionId();
2691 if ( $thisCacheRevisionId === null ) {
2692 $this->setCacheRevisionId( $sourceCacheRevisionId );
2693 } elseif ( $sourceCacheRevisionId !== $thisCacheRevisionId ) {
2694 // May throw an exception here in the future
2696 __METHOD__ . ": conflicting revision IDs " .
2697 "$thisCacheRevisionId and $sourceCacheRevisionId"
2698 );
2699 }
2700 }
2701 if ( $source->mCacheExpiry !== null ) {
2702 $this->updateCacheExpiry( $source->mCacheExpiry );
2703 }
2704
2705 foreach ( self::SPECULATIVE_FIELDS as $field ) {
2706 if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
2707 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
2708 }
2709 $this->$field = self::useMaxValue( $this->$field, $source->$field );
2710 }
2711
2712 $this->mParseStartTime = self::useEachMinValue(
2713 $this->mParseStartTime,
2714 $source->mParseStartTime
2715 );
2716
2717 $this->mTimeProfile = self::useEachTotalValue(
2718 $this->mTimeProfile,
2719 $source->mTimeProfile
2720 );
2721
2722 $this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
2723 $this->mParseUsedOptions = self::mergeMap( $this->mParseUsedOptions, $source->mParseUsedOptions );
2724
2725 // TODO: maintain per-slot limit reports!
2726 if ( !$this->mLimitReportData ) {
2727 $this->mLimitReportData = $source->mLimitReportData;
2728 }
2729 if ( !$this->mLimitReportJSData ) {
2730 $this->mLimitReportJSData = $source->mLimitReportJSData;
2731 }
2732 }
2733
2740 public function mergeHtmlMetaDataFrom( ParserOutput $source ): void {
2741 // HTML and HTTP
2742 $this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
2743 $this->addModules( $source->getModules() );
2744 $this->addModuleStyles( $source->getModuleStyles() );
2745 $this->mJsConfigVars = self::mergeMapStrategy( $this->mJsConfigVars, $source->mJsConfigVars );
2746 if ( $source->mMaxAdaptiveExpiry !== null ) {
2747 $this->updateRuntimeAdaptiveExpiry( $source->mMaxAdaptiveExpiry, $source->getCacheExpirySource() );
2748 }
2749 if ( $source->mCacheExpiry !== null ) {
2750 // Deliberately not using ::getCacheExpiry() here, which can get
2751 // quantized in MiserMode.
2752 $this->updateCacheExpiry( $source->mCacheExpiry, $source->getCacheExpirySource() );
2753 }
2754 $this->mExtraStyleSrcs = self::mergeList(
2755 $this->mExtraStyleSrcs,
2756 $source->getExtraCSPStyleSrcs()
2757 );
2758 $this->mExtraScriptSrcs = self::mergeList(
2759 $this->mExtraScriptSrcs,
2760 $source->getExtraCSPScriptSrcs()
2761 );
2762 $this->mExtraDefaultSrcs = self::mergeList(
2763 $this->mExtraDefaultSrcs,
2764 $source->getExtraCSPDefaultSrcs()
2765 );
2766
2767 foreach ( [
2768 // "noindex" always wins!
2769 ParserOutputFlags::INDEX_POLICY,
2770 ParserOutputFlags::NO_INDEX_POLICY,
2771 // Skin control
2772 ParserOutputFlags::NEW_SECTION,
2773 ParserOutputFlags::HIDE_NEW_SECTION,
2774 ParserOutputFlags::NO_GALLERY,
2775 ParserOutputFlags::ENABLE_OOUI,
2776 ParserOutputFlags::PREVENT_CLICKJACKING,
2777 // Selective update
2778 ParserOutputFlags::PREVENT_SELECTIVE_UPDATE,
2779 ] as $flag ) {
2780 // logical OR of $this and $source
2781 if ( $source->getOutputFlag( $flag ) ) {
2782 $this->setOutputFlag( $flag );
2783 }
2784 }
2785
2786 $tocData = $this->getTOCData();
2787 $sourceTocData = $source->getTOCData();
2788 if ( $tocData !== null ) {
2789 if ( $sourceTocData !== null ) {
2790 // T327429: Section merging is broken, since it doesn't respect
2791 // global numbering, but there are tests which expect section
2792 // metadata to be concatenated.
2793 // There should eventually be a deprecation warning here.
2794 foreach ( $sourceTocData->getSections() as $s ) {
2795 $tocData->addSection( $s );
2796 }
2797 }
2798 } elseif ( $sourceTocData !== null ) {
2799 $this->setTOCData( $sourceTocData );
2800 }
2801
2802 // XXX: we don't want to concatenate title text, so first write wins.
2803 // We should use the first *modified* title text, but we don't have the original to check.
2804 if ( $this->mTitleText === '' ) {
2805 $this->mTitleText = $source->mTitleText;
2806 $this->mDisplayTitleParts = $source->mDisplayTitleParts;
2807 }
2808
2809 // class names are stored in array keys
2810 $this->mWrapperDivClasses = self::mergeMap(
2811 $this->mWrapperDivClasses,
2812 $source->mWrapperDivClasses
2813 );
2814
2815 // NOTE: last write wins, same as within one ParserOutput
2816 foreach ( $source->getIndicators() as $id => $content ) {
2817 $this->setIndicator( $id, $content );
2818 }
2819
2820 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
2821 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
2822 // kinds of extension data to be merged in different ways.
2823 $this->mExtensionData = self::mergeMapStrategy(
2824 $this->mExtensionData,
2825 $source->mExtensionData
2826 );
2827 }
2828
2836 foreach ( ParserOutputLinkTypes::cases() as $linkType ) {
2837 foreach ( $source->getLinkList( $linkType ) as $linkItem ) {
2838 $this->appendLinkList( $linkType, $linkItem );
2839 }
2840 }
2841 $this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
2842
2843 // TODO: add a $mergeStrategy parameter to setPageProperty to allow different
2844 // kinds of properties to be merged in different ways.
2845 // (Model this after ::appendJsConfigVar(); use ::mergeMapStrategy here)
2846 $this->mProperties = self::mergeMap( $this->mProperties, $source->getPageProperties() );
2847
2848 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
2849 $this->mExtensionData = self::mergeMapStrategy(
2850 $this->mExtensionData,
2851 $source->mExtensionData
2852 );
2853 }
2854
2864 public function collectMetadata( ContentMetadataCollector $metadata ): void {
2865 // Uniform handling of all boolean flags: they are OR'ed together.
2866 $flags = array_keys(
2867 $this->mFlags + array_flip( ParserOutputFlags::values() )
2868 );
2869 foreach ( $flags as $name ) {
2870 $name = (string)$name;
2871 if ( $this->getOutputFlag( $name ) ) {
2872 $metadata->setOutputFlag( $name );
2873 }
2874 }
2875
2876 // Uniform handling of string sets: they are unioned.
2877 // (This includes modules, style modes, and CSP src.)
2878 foreach ( ParserOutputStringSets::values() as $name ) {
2879 $metadata->appendOutputStrings(
2880 $name, $this->getOutputStrings( $name )
2881 );
2882 }
2883
2884 foreach ( $this->mCategories as $cat => $key ) {
2885 // Numeric category strings are going to come out of the
2886 // `mCategories` array as ints; cast back to string.
2887 // Also convert back to a LinkTarget!
2888 $lt = TitleValue::tryNew( NS_CATEGORY, (string)$cat );
2889 $metadata->addCategory( $lt, $key );
2890 }
2891
2892 foreach ( $this->mLinks as $ns => $arr ) {
2893 foreach ( $arr as $dbk => $id ) {
2894 // Numeric titles are going to come out of the
2895 // `mLinks` array as ints; cast back to string.
2896 $lt = TitleValue::tryNew( $ns, (string)$dbk );
2897 $metadata->addLink( $lt, $id );
2898 }
2899 }
2900
2901 foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
2902 foreach ( $arr as $dbk => $ignore ) {
2903 $lt = TitleValue::tryNew( NS_MAIN, (string)$dbk, '', $prefix );
2904 $metadata->addLink( $lt );
2905 }
2906 }
2907
2908 foreach ( $this->mLinksSpecial as $dbk => $ignore ) {
2909 // Numeric titles are going to come out of the
2910 // `mLinksSpecial` array as ints; cast back to string.
2911 $lt = TitleValue::tryNew( NS_SPECIAL, (string)$dbk );
2912 $metadata->addLink( $lt );
2913 }
2914
2915 foreach ( $this->mImages as $name => $ignore ) {
2916 // Numeric titles come out of mImages as ints.
2917 $lt = TitleValue::tryNew( NS_FILE, (string)$name );
2918 $props = $this->mFileSearchOptions[$name] ?? [];
2919 $metadata->addImage( $lt, $props['time'] ?? null, $props['sha1'] ?? null );
2920 }
2921
2922 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
2923 # language links can have fragments!
2924 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
2925 $lt = TitleValue::tryNew( NS_MAIN, $title, $frag, (string)$lang );
2926 $metadata->addLanguageLink( $lt );
2927 }
2928
2929 foreach ( $this->mJsConfigVars as $key => $value ) {
2930 // Numeric keys and items are going to come out of the
2931 // `mJsConfigVars` array as ints; cast back to string.
2932 $key = (string)$key;
2933 if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
2934 self::collectMapStrategy( $value, static fn ( $v, $s ) =>
2935 $metadata->appendJsConfigVar( $key, $v, $s )
2936 );
2937 } elseif ( $metadata instanceof ParserOutput &&
2938 array_key_exists( $key, $metadata->mJsConfigVars )
2939 ) {
2940 // This behavior is deprecated, will likely result in
2941 // incorrect output, and we'll eventually emit a
2942 // warning here---but at the moment this is usually
2943 // caused by limitations in Parsoid and/or use of
2944 // the ParserAfterParse hook: T303015#7770480
2945 $metadata->mJsConfigVars[$key] = $value;
2946 $metadata->setOutputFlag( ParserOutputFlags::PREVENT_SELECTIVE_UPDATE );
2947 } else {
2948 $metadata->setJsConfigVar( $key, $value );
2949 }
2950 }
2951 foreach ( $this->mExtensionData as $key => $value ) {
2952 // Numeric keys and items are going to come out of the array as
2953 // ints, cast back to string.
2954 $key = (string)$key;
2955 if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
2956 self::collectMapStrategy( $value, static fn ( $v, $s ) =>
2957 $metadata->appendExtensionData( $key, $v, $s )
2958 );
2959 } elseif ( $metadata instanceof ParserOutput &&
2960 array_key_exists( $key, $metadata->mExtensionData )
2961 ) {
2962 // This behavior is deprecated, will likely result in
2963 // incorrect output, and we'll eventually emit a
2964 // warning here---but at the moment this is usually
2965 // caused by limitations in Parsoid and/or use of
2966 // the ParserAfterParse hook: T303015#7770480
2967 $metadata->mExtensionData[$key] = $value;
2968 } else {
2969 $metadata->setExtensionData( $key, $value );
2970 }
2971 }
2972 foreach ( $this->mExternalLinks as $url => $ignore ) {
2973 $metadata->addExternalLink( (string)$url );
2974 }
2975 foreach ( $this->mProperties as $prop => $value ) {
2976 // Numeric properties are going to come out of the array as ints
2977 $prop = (string)$prop;
2978 if ( is_string( $value ) ) {
2979 $metadata->setUnsortedPageProperty( $prop, $value );
2980 } elseif ( is_numeric( $value ) ) {
2981 $metadata->setNumericPageProperty( $prop, $value );
2982 } else {
2983 // Deprecated, but there are still sites which call
2984 // ::setPageProperty() with "unusual" values (T374046)
2985 wfDeprecated( __METHOD__ . ' with unusual page property', '1.45' );
2986 }
2987 }
2988 foreach ( $this->mLimitReportData as $key => $value ) {
2989 $metadata->setLimitReportData( (string)$key, $value );
2990 }
2991 foreach ( $this->getIndicators() as $id => $content ) {
2992 $metadata->setIndicator( (string)$id, $content );
2993 }
2994
2995 // ParserOutput-only fields; maintained "behind the curtain"
2996 // since Parsoid doesn't have to know about them.
2997 //
2998 // In production use, the $metadata supplied to this method
2999 // will almost always be an instance of ParserOutput, passed to
3000 // Parsoid by core when parsing begins and returned to core by
3001 // Parsoid as a ContentMetadataCollector (Parsoid's name for
3002 // ParserOutput) when DataAccess::parseWikitext() is called.
3003 //
3004 // We may use still Parsoid's StubMetadataCollector for testing or
3005 // when running Parsoid in standalone mode, so forcing a downcast
3006 // here would lose some flexibility.
3007
3008 if ( $metadata instanceof ParserOutput ) {
3009 foreach ( $this->getUsedOptions() as $opt ) {
3010 $metadata->recordOption( $opt );
3011 }
3012 $metadata->mHeadItems = self::mergeMixedList(
3013 $metadata->mHeadItems, $this->mHeadItems
3014 );
3015 if ( $this->mMaxAdaptiveExpiry !== null ) {
3016 $metadata->updateRuntimeAdaptiveExpiry( $this->mMaxAdaptiveExpiry, $this->getCacheExpirySource() );
3017 }
3018 if ( $this->mCacheExpiry !== null ) {
3019 // Deliberately not using ::getCacheExpiry() here, which can get
3020 // quantized in MiserMode.
3021 $metadata->updateCacheExpiry( $this->mCacheExpiry, $this->getCacheExpirySource() );
3022 }
3023 if ( $this->mTimestamp !== null ) {
3024 $metadata->setRevisionTimestamp(
3025 self::useMaxValue(
3026 $this->mTimestamp, $metadata->getRevisionTimestamp()
3027 )
3028 );
3029 }
3030 if ( $this->mCacheTime !== '' ) {
3031 $metadata->setCacheTime( $this->mCacheTime );
3032 }
3033 if ( $this->mCacheRevisionId !== null ) {
3034 $metadata->setCacheRevisionId( $this->mCacheRevisionId );
3035 }
3036 // T293514: We should use the first *modified* title text, but
3037 // we don't have the original to check.
3038 $otherTitle = $metadata->getTitleText();
3039 if ( $otherTitle === '' ) {
3040 $metadata->mTitleText = $this->mTitleText;
3041 $metadata->mDisplayTitleParts = $this->mDisplayTitleParts;
3042 }
3043 // class names are stored in array keys
3044 $metadata->mWrapperDivClasses = self::mergeMap(
3045 $metadata->mWrapperDivClasses,
3046 $this->mWrapperDivClasses
3047 );
3048 // T327429: Section merging is broken, since it doesn't respect
3049 // global numbering, but there are tests which expect section
3050 // metadata to be concatenated.
3051 // There should eventually be a deprecation warning here.
3052 $tocData = $this->getTOCData();
3053 $otherTocData = $metadata->getTOCData();
3054 if ( $otherTocData !== null ) {
3055 if ( $tocData !== null ) {
3056 foreach ( $tocData->getSections() as $s ) {
3057 $otherTocData->addSection( clone $s );
3058 }
3059 }
3060 } elseif ( $tocData !== null ) {
3061 $metadata->setTOCData( clone $tocData );
3062 }
3063 foreach (
3064 [
3065 ParserOutputLinkTypes::TEMPLATE,
3066 ParserOutputLinkTypes::EXISTENCE,
3067 ] as $linkType ) {
3068 foreach ( $this->getLinkList( $linkType ) as $linkItem ) {
3069 $metadata->appendLinkList( $linkType, $linkItem );
3070 }
3071 }
3072 foreach ( $this->mWarningMsgs as $key => $msg ) {
3073 $metadata->addWarningMsgVal( $msg, (string)$key );
3074 }
3075 // mWarnings is deprecated, but keep it around
3076 foreach ( $this->mWarnings as $str => $ignore ) {
3077 $metadata->mWarnings[$str] = 1;
3078 }
3079 // Final render ID should be a function of all component POs.
3080 // In order to make this symmetric w/r/t source and target,
3081 // use the lexicographically first one first.
3082 if ( $this->getRenderId() !== null ) {
3083 $renderIds = [
3084 $this->getRenderId(), $metadata->getRenderId() ?? ''
3085 ];
3086 sort( $renderIds, SORT_STRING );
3087 $metadata->setRenderId( implode( '', $renderIds ) );
3088 }
3089
3090 foreach ( self::SPECULATIVE_FIELDS as $field ) {
3091 if ( $this->$field && $metadata->$field && $this->$field !== $metadata->$field ) {
3092 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
3093 }
3094 $metadata->$field = self::useMaxValue( $this->$field, $metadata->$field );
3095 }
3096
3097 $metadata->mParseStartTime = self::useEachMinValue(
3098 $this->mParseStartTime,
3099 $metadata->mParseStartTime
3100 );
3101
3102 $metadata->mTimeProfile = self::useEachTotalValue(
3103 $this->mTimeProfile,
3104 $metadata->mTimeProfile
3105 );
3106 // TODO: maintain per-slot limit reports!
3107 if ( !$metadata->mLimitReportData ) {
3108 $metadata->mLimitReportData = $this->mLimitReportData;
3109 }
3110 if ( !$metadata->mLimitReportJSData ) {
3111 $metadata->mLimitReportJSData = $this->mLimitReportJSData;
3112 }
3113 }
3114 }
3115
3116 private static function mergeMixedList( array $a, array $b ): array {
3117 return array_unique( array_merge( $a, $b ), SORT_REGULAR );
3118 }
3119
3120 private static function mergeList( array $a, array $b ): array {
3121 return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
3122 }
3123
3124 private static function mergeMap( array $a, array $b ): array {
3125 return array_replace( $a, $b );
3126 }
3127
3131 private static function makeMapStrategy( string|int $value, MergeStrategy $strategy ): array {
3132 $base = [ self::MW_MERGE_STRATEGY_KEY => $strategy->value ];
3133 switch ( $strategy ) {
3134 case MergeStrategy::UNION:
3135 return [ $value => true, ...$base ];
3136 case MergeStrategy::SUM:
3137 Assert::parameterType( 'integer', $value, '$value' );
3138 return [ 'value' => $value, ...$base ];
3139 default:
3140 throw new InvalidArgumentException( "Unknown merge strategy {$strategy->value}" );
3141 }
3142 }
3143
3152 private static function collectMapStrategy( array $map, callable $f ): void {
3153 $strategy = MergeStrategy::from(
3154 $map[self::MW_MERGE_STRATEGY_KEY]
3155 );
3156 foreach ( $map as $key => $value ) {
3157 if ( $key === self::MW_MERGE_STRATEGY_KEY ) {
3158 continue;
3159 }
3160 switch ( $strategy ) {
3161 case MergeStrategy::UNION:
3162 $f( $key, $strategy ); // ignore value
3163 break;
3164 case MergeStrategy::SUM:
3165 $f( $value, $strategy ); // ignore key
3166 break;
3167 }
3168 }
3169 }
3170
3171 private static function mergeMapStrategy( array $a, array $b ): array {
3172 foreach ( $b as $key => $bValue ) {
3173 if ( !array_key_exists( $key, $a ) ) {
3174 $a[$key] = $bValue;
3175 } elseif (
3176 is_array( $a[$key] ) &&
3177 isset( $a[$key][self::MW_MERGE_STRATEGY_KEY] ) &&
3178 isset( $bValue[self::MW_MERGE_STRATEGY_KEY] )
3179 ) {
3180 $strategy = $bValue[self::MW_MERGE_STRATEGY_KEY];
3181 if ( $strategy !== $a[$key][self::MW_MERGE_STRATEGY_KEY] ) {
3182 throw new InvalidArgumentException( "Conflicting merge strategy for $key" );
3183 }
3184 $strategy = MergeStrategy::from( $strategy );
3185 switch ( $strategy ) {
3186 case MergeStrategy::UNION:
3187 // Note the array_merge is *not* safe to use here, because
3188 // the $bValue is expected to be a map from items to `true`.
3189 // If the item is a numeric string like '1' then array_merge
3190 // will convert it to an integer and renumber the array!
3191 $a[$key] = array_replace( $a[$key], $bValue );
3192 break;
3193 case MergeStrategy::SUM:
3194 $a[$key]['value'] += $b[$key]['value'];
3195 break;
3196 default:
3197 throw new InvalidArgumentException( "Unknown merge strategy {$strategy->value}" );
3198 }
3199 } else {
3200 $valuesSame = ( $a[$key] === $bValue );
3201 if ( ( !$valuesSame ) &&
3202 is_object( $a[$key] ) &&
3203 is_object( $bValue )
3204 ) {
3205 $jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
3206 $valuesSame = ( $jsonCodec->toJsonArray( $a[$key] ) === $jsonCodec->toJsonArray( $bValue ) );
3207 }
3208 if ( !$valuesSame ) {
3209 // Silently replace for now; in the future will first emit
3210 // a deprecation warning, and then (later) throw.
3211 $a[$key] = $bValue;
3212 }
3213 }
3214 }
3215 return $a;
3216 }
3217
3218 private static function useEachMinValue( array $a, array $b ): array {
3219 $values = [];
3220 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
3221
3222 foreach ( $keys as $k ) {
3223 $values[$k] = min( $a[$k] ?? INF, $b[$k] ?? INF );
3224 }
3225
3226 return $values;
3227 }
3228
3229 private static function useEachTotalValue( array $a, array $b ): array {
3230 $values = [];
3231 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
3232
3233 foreach ( $keys as $k ) {
3234 $values[$k] = ( $a[$k] ?? 0 ) + ( $b[$k] ?? 0 );
3235 }
3236
3237 return $values;
3238 }
3239
3245 private static function useMaxValue( $a, $b ) {
3246 if ( $a === null ) {
3247 return $b;
3248 }
3249
3250 if ( $b === null ) {
3251 return $a;
3252 }
3253
3254 return max( $a, $b );
3255 }
3256
3263 public function toJsonArray(): array {
3264 // WARNING: When changing how this class is serialized, follow the instructions
3265 // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
3266 $data = [
3267 'LanguageLinks' => $this->getLanguageLinksInternal(),
3268 'Categories' => $this->mCategories,
3269 'IndicatorIds' => $this->mIndicatorIds,
3270 'TitleText' => $this->mTitleText,
3271 'Links' => $this->mLinks,
3272 'LinksSpecial' => $this->mLinksSpecial,
3273 'Templates' => $this->mTemplates,
3274 'TemplateIds' => $this->mTemplateIds,
3275 'Images' => $this->mImages,
3276 'FileSearchOptions' => $this->mFileSearchOptions,
3277 'ExternalLinks' => $this->mExternalLinks,
3278 'InterwikiLinks' => $this->mInterwikiLinks,
3279 'ExistenceLinks' => $this->existenceLinks,
3280 'HeadItems' => $this->mHeadItems,
3281 'Modules' => array_keys( $this->mModuleSet ),
3282 'ModuleStyles' => array_keys( $this->mModuleStyleSet ),
3283 'JsConfigVars' => $this->mJsConfigVars,
3284 'Warnings' => $this->mWarnings,
3285 'WarningMsgs' => $this->mWarningMsgs,
3286 'TOCData' => $this->mTOCData,
3287 'Properties' => self::detectAndEncodeBinary( $this->mProperties ),
3288 'Timestamp' => $this->mTimestamp,
3289 // may contain arbitrary structures!
3290 'ExtensionData' => $this->mExtensionData,
3291 'LimitReportData' => $this->mLimitReportData,
3292 'LimitReportJSData' => $this->mLimitReportJSData,
3293 'CacheMessage' => $this->mCacheMessage,
3294 'TimeProfile' => $this->mTimeProfile,
3295 'ParseStartTime' => [], // don't serialize this
3296 'ExtraScriptSrcs' => $this->mExtraScriptSrcs,
3297 'ExtraDefaultSrcs' => $this->mExtraDefaultSrcs,
3298 'ExtraStyleSrcs' => $this->mExtraStyleSrcs,
3299 'SpeculativeRevId' => $this->mSpeculativeRevId,
3300 'SpeculativePageIdUsed' => $this->speculativePageIdUsed,
3301 'RevisionTimestampUsed' => $this->revisionTimestampUsed,
3302 'RevisionUsedSha1Base36' => $this->revisionUsedSha1Base36,
3303 'WrapperDivClasses' => $this->mWrapperDivClasses,
3304 'OutputFlags' => array_keys( $this->mFlags ),
3305 ];
3306 if ( $this->getContentHolder()->hasContent() ) {
3307 $data[ 'ContentHolder' ] = $this->getContentHolder();
3308 }
3309
3310 // Fill in missing fields from parents. Array addition does not override existing fields.
3311 $data += parent::toJsonArray();
3312
3313 // TODO: make more fields optional!
3314
3315 if ( $this->mDisplayTitleParts !== null ) {
3316 $data['DisplayTitleParts'] = $this->mDisplayTitleParts;
3317 }
3318
3319 if ( $this->mMaxAdaptiveExpiry !== null ) {
3320 $data['MaxAdaptiveExpiry'] = $this->mMaxAdaptiveExpiry;
3321 }
3322
3323 return $data;
3324 }
3325
3326 public static function newFromJsonArray( array $json ): ParserOutput {
3327 $parserOutput = new ParserOutput();
3328 $parserOutput->initFromJson( $json );
3329 return $parserOutput;
3330 }
3331
3333 public static function jsonClassHintFor( string $keyName ) {
3334 return match ( $keyName ) {
3335 'TOCData' => Hint::build( TOCData::class, Hint::ONLY_FOR_DECODE ),
3336 'WarningMsgs' => Hint::build( MessageValue::class, Hint::LIST, Hint::ONLY_FOR_DECODE ),
3337 'ContentHolder' => Hint::build( ContentHolder::class ),
3338 default => null,
3339 };
3340 }
3341
3346 protected function initFromJson( array $jsonData ): void {
3347 parent::initFromJson( $jsonData );
3348
3349 // WARNING: When changing how this class is serialized, follow the instructions
3350 // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
3351 // (This includes changing default values when fields are missing.)
3352 if ( isset( $jsonData['ContentHolder'] ) ) {
3353 $this->contentHolder = $jsonData['ContentHolder'];
3354 } else {
3355 // mostly backward compatibility T423701
3356 $pageBundleData = $jsonData['ExtensionData'][self::PARSOID_PAGE_BUNDLE_KEY] ?? null;
3357 if ( $pageBundleData ) {
3358 unset( $jsonData['ExtensionData'][self::PARSOID_PAGE_BUNDLE_KEY] );
3359 $pb = HtmlPageBundle::newFromJsonArray(
3360 $pageBundleData + [ 'html' => $jsonData['Text'] ?? '' ]
3361 );
3362 $siteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
3363 $this->contentHolder = ContentHolder::createFromParsoidPageBundle( $pb, $siteConfig );
3364 } else {
3365 $this->contentHolder = ContentHolder::createFromLegacyString( $jsonData['Text'] ?? '' );
3366 }
3367 if ( !isset( $jsonData['Text'] ) ) {
3368 // Make the content holder empty if 'no content holder and 'Text' was null.
3369 $this->contentHolder->setAsHtmlString( ContentHolder::BODY_FRAGMENT, null );
3370 }
3371 }
3372 $this->mLanguageLinkMap = [];
3373 foreach ( ( $jsonData['LanguageLinks'] ?? [] ) as $l ) {
3374 // T374736: old serialized parser cache entries may
3375 // contain invalid language links; drop them quietly.
3376 // (This code can be removed two LTS releases past 1.45.)
3377 if ( str_contains( $l, ':' ) ) {
3378 $this->addLanguageLink( $l );
3379 }
3380 }
3381 // Default values should match the property default values.
3382 $this->mCategories = $jsonData['Categories'] ?? [];
3383 $this->mIndicatorIds = $jsonData['IndicatorIds'] ?? [];
3384 // backwards compatibility T427622
3385 foreach ( ( $jsonData['Indicators'] ?? [] ) as $id => $value ) {
3386 $this->setIndicator( $id, $value );
3387 }
3388 $this->mTitleText = $jsonData['TitleText'] ?? '';
3389 $this->mDisplayTitleParts = $jsonData['DisplayTitleParts'] ?? null;
3390 $this->mLinks = $jsonData['Links'] ?? [];
3391 $this->mLinksSpecial = $jsonData['LinksSpecial'] ?? [];
3392 $this->mTemplates = $jsonData['Templates'] ?? [];
3393 $this->mTemplateIds = $jsonData['TemplateIds'] ?? [];
3394 $this->mImages = $jsonData['Images'] ?? [];
3395 $this->mFileSearchOptions = $jsonData['FileSearchOptions'] ?? [];
3396 $this->mExternalLinks = $jsonData['ExternalLinks'] ?? [];
3397 $this->mInterwikiLinks = $jsonData['InterwikiLinks'] ?? [];
3398 $this->existenceLinks = $jsonData['ExistenceLinks'] ?? [];
3399 $this->mHeadItems = $jsonData['HeadItems'] ?? [];
3400 $this->mModuleSet = array_fill_keys( $jsonData['Modules'] ?? [], true );
3401 $this->mModuleStyleSet = array_fill_keys( $jsonData['ModuleStyles'] ?? [], true );
3402 $this->mJsConfigVars = $jsonData['JsConfigVars'] ?? [];
3403 $this->mWarnings = $jsonData['Warnings'] ?? [];
3404 $this->mWarningMsgs = $jsonData['WarningMsgs'] ?? [];
3405
3406 // Set flags stored as properties (backward compatibility with MW<1.45)
3407 $this->mFlags = $jsonData['Flags'] ?? [];
3408 if ( $jsonData['NoGallery'] ?? false ) {
3409 $this->setOutputFlag( ParserOutputFlags::NO_GALLERY );
3410 }
3411 if ( $jsonData['EnableOOUI'] ?? false ) {
3412 $this->setOutputFlag( ParserOutputFlags::ENABLE_OOUI );
3413 }
3414 $this->setIndexPolicy( $jsonData['IndexPolicy'] ?? '' );
3415 if ( $jsonData['NewSection'] ?? false ) {
3416 $this->setOutputFlag( ParserOutputFlags::NEW_SECTION );
3417 }
3418 if ( $jsonData['HideNewSection'] ?? false ) {
3419 $this->setOutputFlag( ParserOutputFlags::HIDE_NEW_SECTION );
3420 }
3421 if ( $jsonData['PreventClickjacking'] ?? false ) {
3422 $this->setOutputFlag( ParserOutputFlags::PREVENT_CLICKJACKING );
3423 }
3424 // Set all generic output flags (whether stored as properties or not)
3425 // (This is effectively a logical-OR if these are also serialized
3426 // above.)
3427 foreach ( $jsonData['OutputFlags'] ?? [] as $flagName ) {
3428 $flag = ParserOutputFlags::tryFrom( $flagName );
3429 if ( $flag !== null ) {
3430 $this->setOutputFlag( $flag );
3431 } else {
3432 // T417819: We *should* backport new ParserOutputFlags values
3433 // to avoid reaching this case, but it ought to be safe to drop
3434 // the unknown flags on the floor.
3436 __METHOD__ . " of flag $flagName without forward compatibility",
3437 '1.46'
3438 );
3439 // Preserve non-standard flags for now since they are used in
3440 // serialization test cases.
3441 $this->setOutputFlag( $flagName );
3442 }
3443 }
3444
3445 if ( isset( $jsonData['TOCData'] ) ) {
3446 $this->mTOCData = $jsonData['TOCData'];
3447 // Backward-compatibility with old TOCData encoding (T327439)
3448 // emitted in MW < 1.45
3449 } elseif (
3450 ( $jsonData['Sections'] ?? [] ) !== [] ||
3451 // distinguish "no sections" from "sections not set"
3452 $this->getOutputFlag( 'mw:toc-set' )
3453 ) {
3454 $this->setSections( $jsonData['Sections'] ?? [] );
3455 unset( $this->mFlags['mw:toc-set'] );
3456 if ( isset( $jsonData['TOCExtensionData'] ) ) {
3457 $tocData = $this->getTOCData(); // created by setSections() above
3458 foreach ( $jsonData['TOCExtensionData'] as $key => $value ) {
3459 $tocData->setExtensionData( (string)$key, $value );
3460 }
3461 }
3462 }
3463 // backward-compatibility: convert page properties to their
3464 // 'database representation'. We haven't permitted non-string
3465 // non-numeric values since 1.45.
3466 $this->mProperties = [];
3467 foreach (
3468 self::detectAndDecodeBinary( $jsonData['Properties'] ?? [] )
3469 as $k => $v
3470 ) {
3471 if ( is_int( $v ) || is_float( $v ) || is_string( $v ) ) {
3472 $this->mProperties[$k] = $v;
3473 } elseif ( is_bool( $v ) ) {
3474 $this->mProperties[$k] = (int)$v;
3475 } elseif ( $v === null ) {
3476 $this->mProperties[$k] = '';
3477 } elseif ( is_array( $v ) ) {
3478 $this->mProperties[$k] = 'Array';
3479 } else {
3480 $this->mProperties[$k] = strval( $v );
3481 }
3482 }
3483 $this->mTimestamp = $jsonData['Timestamp'] ?? null;
3484 $this->mExtensionData = $jsonData['ExtensionData'] ?? [];
3485 $this->mLimitReportData = $jsonData['LimitReportData'] ?? [];
3486 $this->mLimitReportJSData = $jsonData['LimitReportJSData'] ?? [];
3487 $this->mCacheMessage = $jsonData['CacheMessage'] ?? '';
3488 $this->mParseStartTime = []; // invalid after reloading
3489 $this->mTimeProfile = $jsonData['TimeProfile'] ?? [];
3490 $this->mExtraScriptSrcs = $jsonData['ExtraScriptSrcs'] ?? [];
3491 $this->mExtraDefaultSrcs = $jsonData['ExtraDefaultSrcs'] ?? [];
3492 $this->mExtraStyleSrcs = $jsonData['ExtraStyleSrcs'] ?? [];
3493 $this->mSpeculativeRevId = $jsonData['SpeculativeRevId'] ?? null;
3494 $this->speculativePageIdUsed = $jsonData['SpeculativePageIdUsed'] ?? null;
3495 $this->revisionTimestampUsed = $jsonData['RevisionTimestampUsed'] ?? null;
3496 $this->revisionUsedSha1Base36 = $jsonData['RevisionUsedSha1Base36'] ?? null;
3497 $this->mWrapperDivClasses = $jsonData['WrapperDivClasses'] ?? [];
3498 $this->mMaxAdaptiveExpiry = $jsonData['MaxAdaptiveExpiry'] ?? null;
3499 }
3500
3510 private static function detectAndEncodeBinary( array $properties ) {
3511 foreach ( $properties as $key => $value ) {
3512 if ( is_string( $value ) ) {
3513 if ( !mb_detect_encoding( $value, 'UTF-8', true ) ) {
3514 $properties[$key] = [
3515 // T313818: This key name conflicts with JsonCodec
3516 '_type_' => 'string',
3517 '_encoding_' => 'base64',
3518 '_data_' => base64_encode( $value ),
3519 ];
3520 }
3521 }
3522 }
3523
3524 return $properties;
3525 }
3526
3535 private static function detectAndDecodeBinary( array $properties ) {
3536 foreach ( $properties as $key => $value ) {
3537 if ( is_array( $value ) && isset( $value['_encoding_'] ) ) {
3538 if ( $value['_encoding_'] === 'base64' ) {
3539 $properties[$key] = base64_decode( $value['_data_'] );
3540 }
3541 }
3542 }
3543
3544 return $properties;
3545 }
3546
3547 public function __clone() {
3548 // It seems that very little of this object needs to be explicitly deep-cloned
3549 // while keeping copies reasonably separated.
3550 // Most of the non-scalar properties of this object are either
3551 // - (potentially multi-nested) arrays of scalars (which get deep-cloned), or
3552 // - arrays that may contain arbitrary elements (which don't necessarily get
3553 // deep-cloned), but for which no particular care elsewhere is given to
3554 // copying their references around (e.g. mJsConfigVars).
3555 // Hence, we are not going out of our way to ensure that the references to innermost
3556 // objects that may appear in a ParserOutput are unique. If that becomes the
3557 // expectation at any point, this method will require updating as well.
3558 // The exception is TOCData (which is an object), which we clone explicitly.
3559 if ( $this->mTOCData ) {
3560 $this->mTOCData = clone $this->mTOCData;
3561 }
3562 $this->contentHolder = clone $this->contentHolder;
3563 }
3564
3572 public function getContentHolderText(): string {
3573 $html = $this->contentHolder->getAsHtmlString( ContentHolder::BODY_FRAGMENT );
3574 if ( $html === null ) {
3575 throw new LogicException( 'This ParserOutput contains no text!' );
3576 }
3577 return $html;
3578 }
3579
3590 public function setContentHolderText( ?string $text ): void {
3591 $this->contentHolder->setAsHtmlString( ContentHolder::BODY_FRAGMENT, $text );
3592 }
3593
3595 private static function normalizeForObjectEquality(): array {
3596 return [
3597 'mFlags' => static function ( $v ) {
3598 ksort( $v );
3599 return $v;
3600 },
3601 ];
3602 }
3603}
3604
3606class_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.
getModuleStyles( $filter=false)
Get the list of style-only modules to load on this page.
getJsConfigVars()
Get the javascript config vars to include on this page.
getModules( $filter=false)
Get the list of modules to include 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?
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.
setDisplayTitleParts(string|HtmlArmor $nsText, string|HtmlArmor $nsSeparator, string|HtmlArmor $mainText, string|HtmlArmor|null $combinedText=null)
Override the title to be used for display, specifying the namespace, separator, and main title portio...
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,...
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)
getDisplayTitleParts()
Get the title to be used for display, split into namespace, separator, and main title portions.
__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.
setDisplayTitlePartsHtml(string $nsTextHtml, string $nsSeparatorHtml, string $mainTextHtml, ?string $combinedTextHtml=null)
Override the title to be used for display, specifying the namespace, separator, and main title portio...
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.
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.
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.
getTitleText()
This is a formatted HTML string; use ::getDisplayTitleParts() to obtain the title split into namespac...
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.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:18
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, 'EnableChunkedUploads'=> 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'=> '', 'SharedUploadDBschema'=> null, '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'=>[], 'DefaultLockManager'=> null, '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'=> true, '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 -l $lang -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'MaxAnimatedWebPArea'=> 12500000, 'WebPThumbnailType'=>['webp', 'image/webp',], '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'=>[], 'RemoteVirtualDomainsMapping'=>[], '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, 'SplitParsoidParserCache'=> true, '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, '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',],],], 'EnableSectionShare'=> false, '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, ], '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' => [ ], 'RestrictUserPageEditing' => false, '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', 'editmywatchlist' => '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', 'editmywatchlist' => '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', 'managesessions' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], '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, '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, ], 'RestrictedTagViewRights' => [ ], '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' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'RestTermsOfServiceUrl' => null, '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, 'HTTPUserAgentContact' => false, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => 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, 'ReturnExperimentalPFragmentTypes' => [ ], 'UseParsoidLinksUpdate' => null, 'UseParsoidMessages' => true, ], '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', 'SharedUploadDBschema' => [ 'string', 'null', ], 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], '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', ], 'WebPThumbnailType' => 'array', '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', 'RemoteVirtualDomainsMapping' => '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', 'SplitParsoidParserCache' => 'boolean', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => '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', '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', 'RestrictUserPageEditing' => 'boolean', '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', 'ReauthenticateForActions' => 'object', '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', 'RestrictedTagViewRights' => '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', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'HTTPUserAgentContact' => [ 'string', 'boolean', ], 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', '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', 'ReturnExperimentalPFragmentTypes' => 'array', 'UseParsoidLinksUpdate' => [ 'boolean', 'null', ], 'UseParsoidMessages' => [ 'boolean', 'null', ], ], 'mergeStrategy' => [ 'WebPThumbnailType' => 'replace', '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', 'RestExternalModules' => 'array_replace_recursive', ], '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', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], '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', ], 'skipRedirects' => [ 'type' => 'bool', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], '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', ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'availability' => [ 'type' => 'string', ], ], 'required' => [ 'availability', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], '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