MediaWiki master
ParserOutput.php
Go to the documentation of this file.
1<?php
2declare( strict_types = 1 );
3
9namespace MediaWiki\Parser;
10
11use InvalidArgumentException;
12use LogicException;
20use UnhandledMatchError;
21use Wikimedia\Assert\Assert;
22use Wikimedia\Bcp47Code\Bcp47Code;
23use Wikimedia\Bcp47Code\Bcp47CodeValue;
26use Wikimedia\Parsoid\Core\ContentMetadataCollector;
27use Wikimedia\Parsoid\Core\ContentMetadataCollectorCompat;
28use Wikimedia\Parsoid\Core\HtmlPageBundle;
29use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
30use Wikimedia\Parsoid\Core\MergeStrategy;
31use Wikimedia\Parsoid\Core\TOCData;
32
83class ParserOutput extends CacheTime implements ContentMetadataCollector {
84 // This is used to break cyclic dependencies and allow a measure
85 // of compatibility when new methods are added to ContentMetadataCollector
86 // by Parsoid.
87 use ContentMetadataCollectorCompat;
88
93 public const PARSOID_PAGE_BUNDLE_KEY = 'parsoid-page-bundle';
94
99 public const MW_MERGE_STRATEGY_KEY = '_mw-strategy';
100
112 public const MW_MERGE_STRATEGY_UNION = MergeStrategy::UNION;
113
114 private ContentHolder $contentHolder;
115
119 private array $mLanguageLinkMap = [];
120
124 private array $mCategories = [];
125
129 private array $mIndicators = [];
130
134 private string $mTitleText;
135
140 private array $mLinks = [];
141
146 private array $mLinksSpecial = [];
147
152 private array $mTemplates = [];
153
158 private array $mTemplateIds = [];
159
163 private array $mImages = [];
164
168 private array $mFileSearchOptions = [];
169
173 private array $mExternalLinks = [];
174
179 private array $mInterwikiLinks = [];
180
184 private array $existenceLinks = [];
185
189 private bool $mNewSection = false;
190
194 private bool $mHideNewSection = false;
195
199 private bool $mNoGallery = false;
200
204 private array $mHeadItems = [];
205
209 private array $mModuleSet = [];
210
214 private array $mModuleStyleSet = [];
215
219 private array $mJsConfigVars = [];
220
226 private array $mWarnings = [];
227
232 private array $mWarningMsgs = [];
233
237 private ?TOCData $mTOCData = null;
238
242 private array $mProperties = [];
243
247 private ?string $mTimestamp = null;
248
252 private bool $mEnableOOUI = false;
253
257 private bool $mIndexSet = false;
258
262 private bool $mNoIndexSet = false;
263
267 private array $mExtensionData = [];
268
272 private array $mLimitReportData = [];
273
275 private array $mLimitReportJSData = [];
276
278 private string $mCacheMessage = '';
279
283 private array $mParseStartTime = [];
284
288 private array $mTimeProfile = [];
289
295 private bool $mPreventClickjacking = false;
296
300 private array $mExtraScriptSrcs = [];
301
305 private array $mExtraDefaultSrcs = [];
306
310 private array $mExtraStyleSrcs = [];
311
315 private $mFlags = [];
316
317 private const SPECULATIVE_FIELDS = [
318 'speculativePageIdUsed',
319 'mSpeculativeRevId',
320 'revisionTimestampUsed',
321 ];
322
324 private ?int $mSpeculativeRevId = null;
326 private ?int $speculativePageIdUsed = null;
328 private ?string $revisionTimestampUsed = null;
329
331 private ?string $revisionUsedSha1Base36 = null;
332
337 private array $mWrapperDivClasses = [];
338
343 private ?int $mMaxAdaptiveExpiry = null;
344
345 // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
346 // Current values imply that m=3933.333333 and b=-333.333333
347 // See https://www.nngroup.com/articles/website-response-times/
348 private const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
349 private const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
350 private const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
351 private const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
352 private const MIN_AR_TTL = 15; // min adaptive TTL (for pool counter, and edit stashing)
353
364 public function __construct( ?string $text = null, array $languageLinks = [], array $categoryLinks = [],
365 $unused = false, string $titletext = ''
366 ) {
367 if ( $text === null ) {
368 $this->contentHolder = ContentHolder::createEmpty();
369 } else {
370 $this->contentHolder = ContentHolder::createFromLegacyString( $text );
371 }
372 $this->mCategories = $categoryLinks;
373 $this->mTitleText = $titletext;
374 foreach ( $languageLinks as $ll ) {
375 $this->addLanguageLink( $ll );
376 }
377 // If the content handler does not specify an alternative (by
378 // calling ::resetParseStartTime() at a later point) then use
379 // the creation of the ParserOutput as the "start of parse" time.
380 $this->resetParseStartTime();
381 }
382
388 public function getContentHolder(): ContentHolder {
389 return $this->contentHolder;
390 }
391
395 public function setContentHolder( ContentHolder $contentHolder ) {
396 $this->contentHolder = $contentHolder;
397 }
398
409 public function hasText(): bool {
410 return $this->contentHolder->has( ContentHolder::BODY_FRAGMENT );
411 }
412
422 public function getRawText() {
423 return $this->getContentHolderText();
424 }
425
426 /*
427 * @unstable This method is transitional and will be replaced by a method
428 * in another class, maybe ContentRenderer. It allows us to break our
429 * porting work into two steps; in the first we bring ParserOptions to
430 * to each callsite to ensure it is made available to the
431 * postprocessing pipeline. In the second we move this functionality
432 * into the Content hierarchy and out of ParserOutput, which should become
433 * a pure value object.
434 *
435 * @param ParserOptions $popts
436 * @param array $options (since 1.31) Transformations to apply to the HTML
437 * - allowClone: (bool) Whether to clone the ParserOutput before
438 * applying transformations. Default is true.
439 * - allowTOC: (bool) Show the TOC, assuming there were enough headings
440 * to generate one and `__NOTOC__` wasn't used. Default is true,
441 * but might be statefully overridden.
442 * - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
443 * otherwise the marker will be left in the article (and the skin
444 * will be responsible for replacing or removing it). Default is
445 * true.
446 * - enableSectionEditLinks: (bool) Include section edit links, assuming
447 * section edit link tokens are present in the HTML. Default is true,
448 * but might be statefully overridden.
449 * - userLang: (Language) Language object used for localizing UX messages,
450 * for example the heading of the table of contents. If omitted, will
451 * use the language of the main request context.
452 * - skin: (Skin) Skin object used for transforming section edit links.
453 * - unwrap: (bool) Return text without a wrapper div. Default is false,
454 * meaning a wrapper div will be added if getWrapperDivClass() returns
455 * a non-empty string.
456 * - wrapperDivClass: (string) Wrap the output in a div and apply the given
457 * CSS class to that div. This overrides the output of getWrapperDivClass().
458 * Setting this to an empty string has the same effect as 'unwrap' => true.
459 * - deduplicateStyles: (bool) When true, which is the default, `<style>`
460 * tags with the `data-mw-deduplicate` attribute set are deduplicated by
461 * value of the attribute: all but the first will be replaced by `<link
462 * rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
463 * the scheme-specific-part of the href is the (percent-encoded) value
464 * of the `data-mw-deduplicate` attribute.
465 * - absoluteURLs: (bool) use absolute URLs in all links. Default: false
466 * - includeDebugInfo: (bool) render PP limit report in HTML. Default: false
467 * It is planned to eventually deprecate this $options array and to be able to
468 * pass its content in the $popts ParserOptions.
469 * @return ParserOutput
470 */
471 public function runOutputPipeline( ParserOptions $popts, array $options = [] ): ParserOutput {
472 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
473 $options += [
474 'allowClone' => true,
475 'allowTOC' => true,
476 'injectTOC' => true,
477 'enableSectionEditLinks' => !$this->getOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS ),
478 'userLang' => null,
479 'skin' => null,
480 'unwrap' => false,
481 'wrapperDivClass' => $this->getWrapperDivClass(),
482 'deduplicateStyles' => true,
483 'absoluteURLs' => false,
484 'includeDebugInfo' => false,
485 ];
486 return $pipeline->run( $this, $popts, $options );
487 }
488
494 public function addCacheMessage( string $msg ): void {
495 $this->mCacheMessage .= $msg;
496 }
497
503 public function addWrapperDivClass( $class ): void {
504 $this->mWrapperDivClasses[$class] = true;
505 }
506
511 public function clearWrapperDivClass(): void {
512 $this->mWrapperDivClasses = [];
513 }
514
520 public function getWrapperDivClass(): string {
521 return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
522 }
523
528 public function setSpeculativeRevIdUsed( $id ): void {
529 $this->mSpeculativeRevId = $id;
530 }
531
536 public function getSpeculativeRevIdUsed(): ?int {
537 return $this->mSpeculativeRevId;
538 }
539
544 public function setSpeculativePageIdUsed( $id ): void {
545 $this->speculativePageIdUsed = $id;
546 }
547
552 public function getSpeculativePageIdUsed() {
553 return $this->speculativePageIdUsed;
554 }
555
560 public function setRevisionTimestampUsed( $timestamp ): void {
561 $this->revisionTimestampUsed = $timestamp;
562 }
563
568 public function getRevisionTimestampUsed() {
569 return $this->revisionTimestampUsed;
570 }
571
576 public function setRevisionUsedSha1Base36( $hash ): void {
577 if ( $hash === null ) {
578 return; // e.g. RevisionRecord::getSha1() returned null
579 }
580
581 if (
582 $this->revisionUsedSha1Base36 !== null &&
583 $this->revisionUsedSha1Base36 !== $hash
584 ) {
585 $this->revisionUsedSha1Base36 = ''; // mismatched
586 } else {
587 $this->revisionUsedSha1Base36 = $hash;
588 }
589 }
590
595 public function getRevisionUsedSha1Base36() {
596 return $this->revisionUsedSha1Base36;
597 }
598
604 public function getLanguageLinks() {
605 wfDeprecated( __METHOD__, '1.43' );
606 return $this->getLanguageLinksInternal();
607 }
608
612 private function getLanguageLinksInternal(): array {
613 $result = [];
614 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
615 $result[] = "$lang:$title";
616 }
617 return $result;
618 }
619
621 public function getInterwikiLinks() {
622 wfDeprecated( __METHOD__, '1.43' );
623 return $this->mInterwikiLinks;
624 }
625
633 public function getCategoryNames(): array {
634 # Note that numeric category names get converted to 'int' when
635 # stored as array keys; stringify the keys to ensure they
636 # return to original string form so as not to confuse callers.
637 return array_map( 'strval', array_keys( $this->mCategories ) );
638 }
639
651 public function getCategoryMap(): array {
652 return $this->mCategories;
653 }
654
668 public function getCategorySortKey( string $name ): ?string {
669 // This API avoids exposing the fact that numeric string category
670 // names are going to be converted to 'int' when used as array
671 // keys for the `mCategories` field.
672 return $this->mCategories[$name] ?? null;
673 }
674
679 public function getIndicators(): array {
680 return $this->mIndicators;
681 }
682
683 public function getTitleText(): string {
684 return $this->mTitleText;
685 }
686
691 public function getTOCData(): ?TOCData {
692 return $this->mTOCData;
693 }
694
699 public function getCacheMessage(): string {
700 return $this->mCacheMessage;
701 }
702
707 public function getSections(): array {
708 if ( $this->mTOCData !== null ) {
709 return $this->mTOCData->toLegacy();
710 }
711 // For compatibility
712 return [];
713 }
714
733 public function getLinkList( string|ParserOutputLinkTypes $linkType, ?int $onlyNamespace = null ): array {
734 if ( is_string( $linkType ) ) {
735 $linkType = ParserOutputLinkTypes::from( $linkType );
736 }
737 # Note that fragments are dropped for everything except language links
738 $result = [];
739 switch ( $linkType ) {
740 case ParserOutputLinkTypes::CATEGORY:
741 if ( $onlyNamespace !== null && $onlyNamespace !== NS_CATEGORY ) {
742 return [];
743 }
744 foreach ( $this->mCategories as $dbkey => $sort ) {
745 $result[] = [
746 'link' => new TitleValue( NS_CATEGORY, (string)$dbkey ),
747 'sort' => $sort,
748 ];
749 }
750 break;
751
752 case ParserOutputLinkTypes::EXISTENCE:
753 $links = $onlyNamespace === null ? $this->existenceLinks : [
754 $onlyNamespace => $this->existenceLinks[$onlyNamespace] ?? [],
755 ];
756 foreach ( $links as $ns => $titles ) {
757 foreach ( $titles as $dbkey => $unused ) {
758 $result[] = [
759 'link' => new TitleValue( $ns, (string)$dbkey )
760 ];
761 }
762 }
763 break;
764
765 case ParserOutputLinkTypes::INTERWIKI:
766 // By convention interwiki links belong to NS_MAIN
767 if ( $onlyNamespace !== null && $onlyNamespace !== NS_MAIN ) {
768 return [];
769 }
770 foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
771 foreach ( $arr as $dbkey => $ignore ) {
772 $result[] = [
773 'link' => new TitleValue( NS_MAIN, (string)$dbkey, '', (string)$prefix ),
774 ];
775 }
776 }
777 break;
778
779 case ParserOutputLinkTypes::LANGUAGE:
780 // By convention language links belong to NS_MAIN
781 if ( $onlyNamespace !== null && $onlyNamespace !== NS_MAIN ) {
782 return [];
783 }
784 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
785 # language links can have fragments!
786 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
787 $result[] = [
788 'link' => new TitleValue( NS_MAIN, $title, $frag, (string)$lang ),
789 ];
790 }
791 break;
792
793 case ParserOutputLinkTypes::LOCAL:
794 $links = $onlyNamespace === null ? $this->mLinks : [
795 $onlyNamespace => $this->mLinks[$onlyNamespace] ?? [],
796 ];
797 foreach ( $links as $ns => $arr ) {
798 foreach ( $arr as $dbkey => $id ) {
799 $result[] = [
800 'link' => new TitleValue( $ns, (string)$dbkey ),
801 'pageid' => $id,
802 ];
803 }
804 }
805 break;
806
807 case ParserOutputLinkTypes::MEDIA:
808 if ( $onlyNamespace !== null && $onlyNamespace !== NS_FILE ) {
809 return [];
810 }
811 foreach ( $this->mImages as $dbkey => $ignore ) {
812 $extra = $this->mFileSearchOptions[$dbkey] ?? [];
813 $extra['link'] = new TitleValue( NS_FILE, (string)$dbkey );
814 $result[] = $extra;
815 }
816 break;
817
818 case ParserOutputLinkTypes::SPECIAL:
819 if ( $onlyNamespace !== null && $onlyNamespace !== NS_SPECIAL ) {
820 return [];
821 }
822 foreach ( $this->mLinksSpecial as $dbkey => $ignore ) {
823 $result[] = [
824 'link' => new TitleValue( NS_SPECIAL, (string)$dbkey ),
825 ];
826 }
827 break;
828
829 case ParserOutputLinkTypes::TEMPLATE:
830 $links = $onlyNamespace === null ? $this->mTemplates : [
831 $onlyNamespace => $this->mTemplates[$onlyNamespace] ?? [],
832 ];
833 foreach ( $links as $ns => $arr ) {
834 foreach ( $arr as $dbkey => $pageid ) {
835 $result[] = [
836 'link' => new TitleValue( $ns, (string)$dbkey ),
837 'pageid' => $pageid,
838 // default to invalid/broken revision if this is not present
839 'revid' => $this->mTemplateIds[$ns][$dbkey] ?? 0,
840 ];
841 }
842 }
843 break;
844
845 default:
846 throw new UnhandledMatchError( "Unknown link type " . $linkType->value );
847 }
848 return $result;
849 }
850
864 public function appendLinkList( string|ParserOutputLinkTypes $linkType, array $linkItem ): void {
865 if ( is_string( $linkType ) ) {
866 $linkType = ParserOutputLinkTypes::from( $linkType );
867 }
868 $link = $linkItem['link'];
869 match ( $linkType ) {
870 ParserOutputLinkTypes::CATEGORY =>
871 $this->addCategory( $link, $linkItem['sort'] ?? '' ),
872 ParserOutputLinkTypes::EXISTENCE =>
873 $this->addExistenceDependency( $link ),
874 ParserOutputLinkTypes::INTERWIKI =>
875 $this->addInterwikiLink( $link ),
876 ParserOutputLinkTypes::LANGUAGE =>
877 $this->addLanguageLink( $link ),
878 ParserOutputLinkTypes::LOCAL =>
879 $this->addLink( $link, $linkItem['pageid'] ?? null ),
880 ParserOutputLinkTypes::MEDIA =>
881 $this->addImage( $link, $linkItem['time'] ?? null, $linkItem['sha1'] ?? null ),
882 ParserOutputLinkTypes::SPECIAL =>
883 $this->addLink( $link ),
884 ParserOutputLinkTypes::TEMPLATE =>
885 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
886 $this->addTemplate( $link, $linkItem['pageid'], $linkItem['revid'] ),
887 };
888 }
889
891 public function &getLinks() {
892 wfDeprecated( __METHOD__, '1.43' );
893 return $this->mLinks;
894 }
895
902 public function hasLinks(): bool {
903 foreach ( $this->mLinks as $ns => $arr ) {
904 foreach ( $arr as $dbkey => $id ) {
905 return true;
906 }
907 }
908 return false;
909 }
910
916 public function &getLinksSpecial() {
917 wfDeprecated( __METHOD__, '1.43' );
918 return $this->mLinksSpecial;
919 }
920
922 public function &getTemplates() {
923 wfDeprecated( __METHOD__, '1.43' );
924 return $this->mTemplates;
925 }
926
928 public function &getTemplateIds() {
929 wfDeprecated( __METHOD__, '1.43' );
930 return $this->mTemplateIds;
931 }
932
934 public function &getImages() {
935 wfDeprecated( __METHOD__, '1.43' );
936 return $this->mImages;
937 }
938
944 public function hasImages(): bool {
945 return $this->mImages !== [];
946 }
947
949 public function &getFileSearchOptions() {
950 wfDeprecated( __METHOD__, '1.43' );
951 return $this->mFileSearchOptions;
952 }
953
960 public function &getExternalLinks(): array {
961 return $this->mExternalLinks;
962 }
963
967 public function setNoGallery( $value ): void {
968 $this->mNoGallery = (bool)$value;
969 }
970
974 public function getNoGallery() {
975 return $this->mNoGallery;
976 }
977
981 public function getHeadItems() {
982 return $this->mHeadItems;
983 }
984
988 public function getModules() {
989 return array_keys( $this->mModuleSet );
990 }
991
995 public function getModuleStyles() {
996 return array_keys( $this->mModuleStyleSet );
997 }
998
1006 public function getJsConfigVars( bool $showStrategyKeys = false ) {
1007 $result = $this->mJsConfigVars;
1008 // Don't expose the internal strategy key
1009 foreach ( $result as &$value ) {
1010 if ( is_array( $value ) && !$showStrategyKeys ) {
1011 if ( ( $value[self::MW_MERGE_STRATEGY_KEY] ?? null ) === MergeStrategy::SUM->value ) {
1012 $value = $value['value'];
1013 continue;
1014 }
1015 unset( $value[self::MW_MERGE_STRATEGY_KEY] );
1016 }
1017 }
1018 return $result;
1019 }
1020
1022 public function getWarnings(): array {
1023 // T343048: Don't emit deprecation warnings here until the
1024 // compatibility fallback in ApiParse is removed.
1025 return array_keys( $this->mWarnings );
1026 }
1027
1029 public function getWarningMsgs(): array {
1030 return array_values( $this->mWarningMsgs );
1031 }
1032
1033 public function getIndexPolicy(): string {
1034 // 'noindex' wins if both are set. (T16899)
1035 if ( $this->mNoIndexSet ) {
1036 return 'noindex';
1037 } elseif ( $this->mIndexSet ) {
1038 return 'index';
1039 }
1040 return '';
1041 }
1042
1046 public function getRevisionTimestamp(): ?string {
1047 return $this->mTimestamp;
1048 }
1049
1054 public function getTimestamp() {
1055 wfDeprecated( __METHOD__, '1.42' );
1056 return $this->getRevisionTimestamp();
1057 }
1058
1062 public function getLimitReportData() {
1063 return $this->mLimitReportData;
1064 }
1065
1069 public function getLimitReportJSData() {
1070 return $this->mLimitReportJSData;
1071 }
1072
1076 public function getEnableOOUI() {
1077 return $this->mEnableOOUI;
1078 }
1079
1085 public function getExtraCSPDefaultSrcs() {
1086 return $this->mExtraDefaultSrcs;
1087 }
1088
1094 public function getExtraCSPScriptSrcs() {
1095 return $this->mExtraScriptSrcs;
1096 }
1097
1103 public function getExtraCSPStyleSrcs() {
1104 return $this->mExtraStyleSrcs;
1105 }
1106
1117 public function setRawText( ?string $text ): void {
1118 $this->setContentHolderText( $text );
1119 }
1120
1133 public function setText( $text ) {
1134 wfDeprecated( __METHOD__, '1.42' );
1135 $ret = $this->hasText() ? $this->getContentHolderText() : null;
1136 $this->setContentHolderText( $text );
1137 return $ret;
1138 }
1139
1143 public function setLanguageLinks( $ll ) {
1144 wfDeprecated( __METHOD__, '1.42' );
1145 $old = $this->getLanguageLinksInternal();
1146 $this->mLanguageLinkMap = [];
1147 if ( $ll === null ) { // T376323
1148 wfDeprecated( __METHOD__ . ' with null argument', '1.43' );
1149 }
1150 foreach ( ( $ll ?? [] ) as $l ) {
1151 $this->addLanguageLink( $l );
1152 }
1153 return $old;
1154 }
1155
1157 public function clearLanguageLinks(): void {
1158 $this->mLanguageLinkMap = [];
1159 }
1160
1165 public function setTitleText( string $t ) {
1166 return wfSetVar( $this->mTitleText, $t );
1167 }
1168
1172 public function setTOCData( TOCData $tocData ): void {
1173 $this->mTOCData = $tocData;
1174 }
1175
1180 public function setSections( array $sectionArray ) {
1181 $oldValue = $this->getSections();
1182 $this->setTOCData( TOCData::fromLegacy( $sectionArray ) );
1183 return $oldValue;
1184 }
1185
1199 public function setIndexPolicy( $policy ): string {
1200 $old = $this->getIndexPolicy();
1201 if ( $policy === 'noindex' ) {
1202 $this->mNoIndexSet = true;
1203 } elseif ( $policy === 'index' ) {
1204 $this->mIndexSet = true;
1205 }
1206 return $old;
1207 }
1208
1212 public function setRevisionTimestamp( ?string $timestamp ): void {
1213 $this->mTimestamp = $timestamp;
1214 }
1215
1222 public function setTimestamp( $timestamp ) {
1223 wfDeprecated( __METHOD__, '1.42' );
1224 return wfSetVar( $this->mTimestamp, $timestamp );
1225 }
1226
1242 public function addCategory( $c, $sort = '' ): void {
1243 if ( $c instanceof ParsoidLinkTarget ) {
1244 $c = $c->getDBkey();
1245 }
1246 $this->mCategories[$c] = $sort;
1247 }
1248
1254 public function setCategories( array $c ): void {
1255 $this->mCategories = $c;
1256 }
1257
1264 public function setIndicator( $id, $content ): void {
1265 $this->mIndicators[$id] = $content;
1266 }
1267
1275 public function setEnableOOUI( bool $enable = false ): void {
1276 $this->mEnableOOUI = $enable;
1277 }
1278
1283 public function addLanguageLink( $t ): void {
1284 # Note that fragments are preserved
1285 if ( $t instanceof ParsoidLinkTarget ) {
1286 // Language links are unusual in using 'text' rather than 'db key'
1287 // Note that fragments are preserved.
1288 $lang = $t->getInterwiki();
1289 $title = $t->getText();
1290 if ( $t->hasFragment() ) {
1291 $title .= '#' . $t->getFragment();
1292 }
1293 } else {
1294 [ $lang, $title ] = array_pad( explode( ':', $t, 2 ), -2, '' );
1295 }
1296 if ( $lang === '' ) {
1297 throw new InvalidArgumentException( __METHOD__ . ' without prefix' );
1298 }
1299 $this->mLanguageLinkMap[$lang] ??= $title;
1300 }
1301
1310 public function addWarningMsgVal( MessageSpecifier $mv, ?string $key = null ) {
1311 $mv = MessageValue::newFromSpecifier( $mv );
1312 $key ??= $mv->getKey();
1313 $this->mWarningMsgs[$key] = $mv;
1314 // Ensure callers aren't passing nonserializable arguments: T343048.
1315 $jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
1316 $path = $jsonCodec->detectNonSerializableData( $mv, true );
1317 if ( $path !== null ) {
1318 throw new InvalidArgumentException( __METHOD__ . ": nonserializable" );
1319 }
1320 // For backward compatibility with callers of ::getWarnings()
1321 // and rollback compatibility for ParserCache; don't remove
1322 // until we no longer need rollback compatiblity with MW 1.43.
1323 $s = Message::newFromSpecifier( $mv )
1324 // some callers set the title here?
1325 ->inContentLanguage() // because this ends up in cache
1326 ->text();
1327 $this->mWarnings[$s] = 1;
1328 }
1329
1338 public function addWarningMsg( string $msg, ...$args ): void {
1339 // T227447: Once MessageSpecifier is moved to a library, Parsoid would
1340 // be able to use ::addWarningMsgVal() directly and this method
1341 // could be deprecated and removed.
1342 $this->addWarningMsgVal( MessageValue::new( $msg, $args ) );
1343 }
1344
1348 public function setNewSection( $value ): void {
1349 $this->mNewSection = (bool)$value;
1350 }
1351
1355 public function setHideNewSection( bool $value ): void {
1356 $this->mHideNewSection = $value;
1357 }
1358
1359 public function getHideNewSection(): bool {
1360 return $this->mHideNewSection;
1361 }
1362
1363 public function getNewSection(): bool {
1364 return $this->mNewSection;
1365 }
1366
1375 public static function isLinkInternal( $internal, $url ): bool {
1376 return (bool)preg_match( '/^' .
1377 # If server is proto relative, check also for http/https links
1378 ( str_starts_with( $internal, '//' ) ? '(?:https?:)?' : '' ) .
1379 preg_quote( $internal, '/' ) .
1380 # check for query/path/anchor or end of link in each case
1381 '(?:[\?\/\#]|$)/i',
1382 $url
1383 );
1384 }
1385
1389 public function addExternalLink( $url ): void {
1390 # We don't register links pointing to our own server, unless... :-)
1391 $config = MediaWikiServices::getInstance()->getMainConfig();
1392 $server = $config->get( MainConfigNames::Server );
1393 $registerInternalExternals = $config->get( MainConfigNames::RegisterInternalExternals );
1394 $ignoreDomains = $config->get( MainConfigNames::ExternalLinksIgnoreDomains );
1395
1396 # Replace unnecessary URL escape codes with the referenced character
1397 # This prevents spammers from hiding links from the filters
1398 $url = Parser::normalizeLinkUrl( $url );
1399
1400 $registerExternalLink = true;
1401 if ( !$registerInternalExternals ) {
1402 $registerExternalLink = !self::isLinkInternal( $server, $url );
1403 }
1404 if (
1405 MediaWikiServices::getInstance()->getUrlUtils()->matchesDomainList( $url, $ignoreDomains )
1406 ) {
1407 $registerExternalLink = false;
1408 }
1409 if ( $registerExternalLink ) {
1410 $this->mExternalLinks[$url] = 1;
1411 }
1412 }
1413
1420 public function addLink( ParsoidLinkTarget $link, $id = null ): void {
1421 if ( $link->isExternal() ) {
1422 // Don't record interwikis in pagelinks
1423 $this->addInterwikiLink( $link );
1424 return;
1425 }
1426 $ns = $link->getNamespace();
1427 $dbk = $link->getDBkey();
1428 if ( $ns === NS_MEDIA ) {
1429 // Normalize this pseudo-alias if it makes it down here...
1430 $ns = NS_FILE;
1431 } elseif ( $ns === NS_SPECIAL ) {
1432 // We don't want to record Special: links in the database, so put them in a separate place.
1433 // It might actually be wise to, but we'd need to do some normalization.
1434 $this->mLinksSpecial[$dbk] = 1;
1435 return;
1436 } elseif ( $dbk === '' ) {
1437 // Don't record self links - [[#Foo]]
1438 return;
1439 }
1440 if ( $id === null ) {
1441 // T357048: This actually kills performance; we should batch these.
1442 $page = MediaWikiServices::getInstance()->getPageStore()->getPageForLink( $link );
1443 $id = $page->getId();
1444 }
1445 $this->mLinks[$ns][$dbk] = $id;
1446 }
1447
1454 public function addImage( $name, $timestamp = null, $sha1 = null ): void {
1455 if ( $name instanceof ParsoidLinkTarget ) {
1456 $name = $name->getDBkey();
1457 }
1458 $this->mImages[$name] = 1;
1459 if ( $timestamp !== null && $sha1 !== null ) {
1460 $this->mFileSearchOptions[$name] = [ 'time' => $timestamp, 'sha1' => $sha1 ];
1461 }
1462 }
1463
1471 public function addTemplate( $link, $page_id, $rev_id ): void {
1472 if ( $link->isExternal() ) {
1473 // Will throw an InvalidArgumentException in a future release.
1474 throw new InvalidArgumentException( __METHOD__ . " with interwiki link" );
1475 }
1476 $ns = $link->getNamespace();
1477 $dbk = $link->getDBkey();
1478 // T357048: Parsoid doesn't have page_id
1479 $this->mTemplates[$ns][$dbk] = $page_id;
1480 $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
1481 }
1482
1487 public function addInterwikiLink( $link ): void {
1488 if ( !$link->isExternal() ) {
1489 throw new InvalidArgumentException( 'Non-interwiki link passed, internal parser error.' );
1490 }
1491 $prefix = $link->getInterwiki();
1492 $this->mInterwikiLinks[$prefix][$link->getDBkey()] = 1;
1493 }
1494
1502 public function addExistenceDependency( ParsoidLinkTarget $link ) {
1503 $ns = $link->getNamespace();
1504 $dbk = $link->getDBkey();
1505 // Ignore some kinds of links, as in addLink()
1506 if ( $link->isExternal() || $ns === NS_SPECIAL || $dbk === '' ) {
1507 return;
1508 }
1509 if ( $ns === NS_MEDIA ) {
1510 $ns = NS_FILE;
1511 }
1512 $this->existenceLinks[$ns][$dbk] = true;
1513 }
1514
1522 public function addHeadItem( $section, $tag = false ): void {
1523 if ( $tag !== false ) {
1524 $this->mHeadItems[$tag] = $section;
1525 } else {
1526 $this->mHeadItems[] = $section;
1527 }
1528 }
1529
1534 public function addModules( array $modules ): void {
1535 $modules = array_fill_keys( $modules, true );
1536 $this->mModuleSet = array_merge( $this->mModuleSet, $modules );
1537 }
1538
1543 public function addModuleStyles( array $modules ): void {
1544 $modules = array_fill_keys( $modules, true );
1545 $this->mModuleStyleSet = array_merge( $this->mModuleStyleSet, $modules );
1546 }
1547
1558 public function addJsConfigVars( $keys, $value = null ): void {
1559 wfDeprecated( __METHOD__, '1.38' );
1560 if ( is_array( $keys ) ) {
1561 foreach ( $keys as $key => $value ) {
1562 $this->mJsConfigVars[$key] = $value;
1563 }
1564 return;
1565 }
1566
1567 $this->mJsConfigVars[$keys] = $value;
1568 }
1569
1583 public function setJsConfigVar( string $key, $value ): void {
1584 if (
1585 array_key_exists( $key, $this->mJsConfigVars ) &&
1586 $this->mJsConfigVars[$key] !== $value
1587 ) {
1588 // Ensure that a key is mapped to only a single value in order
1589 // to prevent the resulting array from varying if content
1590 // is parsed in a different order.
1591 throw new InvalidArgumentException( "Multiple conflicting values given for $key" );
1592 }
1593 $this->mJsConfigVars[$key] = $value;
1594 }
1595
1612 public function appendJsConfigVar(
1613 string $key,
1614 $value,
1615 MergeStrategy|string $strategy = MergeStrategy::UNION
1616 ): void {
1617 if ( is_string( $strategy ) ) {
1618 $strategy = MergeStrategy::from( $strategy );
1619 }
1620 $this->mJsConfigVars = self::mergeMapStrategy(
1621 $this->mJsConfigVars,
1622 [ $key => self::makeMapStrategy( $value, $strategy ) ]
1623 );
1624 }
1625
1641 public function addOutputPageMetadata( OutputPage $out ): void {
1642 // This should eventually use the same merge mechanism used
1643 // internally to merge ParserOutputs together.
1644 // (ie: $this->mergeHtmlMetaDataFrom( $out->getMetadata() )
1645 // once preventClickjacking, moduleStyles, modules, jsconfigvars,
1646 // and head items are moved to OutputPage::$metadata)
1647
1648 // Take the strictest click-jacking policy. This is to ensure any one-click features
1649 // such as patrol or rollback on the transcluded special page will result in the wiki page
1650 // disallowing embedding in cross-origin iframes. Articles are generally allowed to be
1651 // embedded. Pages that transclude special pages are expected to be user pages or
1652 // other non-content pages that content re-users won't discover or care about.
1653 $this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
1654
1655 $this->addModuleStyles( $out->getModuleStyles() );
1656
1657 // TODO: Figure out if style modules suffice, or whether the below is needed as well.
1658 // Are there special pages that permit transcluding/including and also have JS modules
1659 // that should be activate on the host page?
1660 $this->addModules( $out->getModules() );
1661 $this->mJsConfigVars = self::mergeMapStrategy(
1662 $this->mJsConfigVars, $out->getJsConfigVars()
1663 );
1664 $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
1665 }
1666
1678 public function setDisplayTitle( string $text ): void {
1679 $this->setTitleText( $text );
1680 $this->setPageProperty( 'displaytitle', $text );
1681 }
1682
1691 public function getDisplayTitle(): string|false {
1692 $t = $this->getTitleText();
1693 if ( $t === '' ) {
1694 return false;
1695 }
1696 return $t;
1697 }
1698
1737 public function getLanguage(): ?Bcp47Code {
1738 // This information is temporarily stored in extension data (T303329)
1739 $code = $this->getExtensionData( 'core:target-lang-variant' );
1740 // This is null if the ParserOutput was cached by MW 1.40 or earlier,
1741 // or not constructed by Parser/ParserCache.
1742 return $code === null ? null : new Bcp47CodeValue( $code );
1743 }
1744
1754 public function setLanguage( Bcp47Code $lang ): void {
1755 $this->setExtensionData( 'core:target-lang-variant', $lang->toBcp47Code() );
1756 }
1757
1764 public function getRedirectHeader(): ?string {
1765 return $this->getExtensionData( 'core:redirect-header' );
1766 }
1767
1772 public function setRedirectHeader( string $html ): void {
1773 $this->setExtensionData( 'core:redirect-header', $html );
1774 }
1775
1784 public function setRenderId( string $renderId ): void {
1785 $this->setExtensionData( 'core:render-id', $renderId );
1786 }
1787
1795 public function getRenderId(): ?string {
1796 // Backward-compatibility with old cache contents
1797 // Can be removed after parser cache contents have expired
1798 $old = $this->getExtensionData( 'parsoid-render-id' );
1799 if ( $old !== null ) {
1800 return ParsoidRenderId::newFromKey( $old )->getUniqueID();
1801 }
1802 return $this->getExtensionData( 'core:render-id' );
1803 }
1804
1809 public function getAllFlags(): array {
1810 return array_keys( $this->mFlags );
1811 }
1812
1905 public function setPageProperty( string $name, string $value ): void {
1906 $this->setUnsortedPageProperty( $name, $value );
1907 }
1908
1923 public function setNumericPageProperty( string $propName, $numericValue ): void {
1924 if ( !is_numeric( $numericValue ) ) {
1925 throw new InvalidArgumentException( __METHOD__ . " with non-numeric value" );
1926 }
1927 // Coerce numeric sort key to a number.
1928 $this->mProperties[$propName] = 0 + $numericValue;
1929 }
1930
1948 public function setUnsortedPageProperty( string $propName, string $value = '' ): void {
1949 $this->mProperties[$propName] = $value;
1950 }
1951
1965 public function getPageProperty( string $name ) {
1966 return $this->mProperties[$name] ?? null;
1967 }
1968
1974 public function unsetPageProperty( string $name ): void {
1975 unset( $this->mProperties[$name] );
1976 }
1977
1983 public function getPageProperties(): array {
1984 return $this->mProperties;
1985 }
1986
2009 public function setOutputFlag( ParserOutputFlags|string $name, bool $val = true ): void {
2010 if ( is_string( $name ) ) {
2011 $flag = ParserOutputFlags::tryFrom( $name );
2012 if ( $flag === null ) {
2014 __METHOD__ . ' with non-standard flag', '1.45'
2015 );
2016 }
2017 } else {
2018 $flag = $name;
2019 $name = $flag->value;
2020 }
2021 switch ( $flag ) {
2022 case ParserOutputFlags::NO_GALLERY:
2023 $this->setNoGallery( $val );
2024 break;
2025
2026 case ParserOutputFlags::ENABLE_OOUI:
2027 $this->setEnableOOUI( $val );
2028 break;
2029
2030 case ParserOutputFlags::NO_INDEX_POLICY:
2031 $this->mNoIndexSet = $val;
2032 break;
2033
2034 case ParserOutputFlags::INDEX_POLICY:
2035 $this->mIndexSet = $val;
2036 break;
2037
2038 case ParserOutputFlags::NEW_SECTION:
2039 $this->setNewSection( $val );
2040 break;
2041
2042 case ParserOutputFlags::HIDE_NEW_SECTION:
2043 $this->setHideNewSection( $val );
2044 break;
2045
2046 case ParserOutputFlags::PREVENT_CLICKJACKING:
2047 $this->setPreventClickjacking( $val );
2048 break;
2049
2050 default:
2051 if ( $val ) {
2052 $this->mFlags[$name] = true;
2053 } else {
2054 unset( $this->mFlags[$name] );
2055 }
2056 break;
2057 }
2058 }
2059
2072 public function getOutputFlag( ParserOutputFlags|string $name ): bool {
2073 if ( is_string( $name ) ) {
2074 $flag = ParserOutputFlags::tryFrom( $name );
2075 } else {
2076 $flag = $name;
2077 $name = $flag->value;
2078 }
2079 switch ( $flag ) {
2080 case ParserOutputFlags::NO_GALLERY:
2081 return $this->getNoGallery();
2082
2083 case ParserOutputFlags::ENABLE_OOUI:
2084 return $this->getEnableOOUI();
2085
2086 case ParserOutputFlags::INDEX_POLICY:
2087 return $this->mIndexSet;
2088
2089 case ParserOutputFlags::NO_INDEX_POLICY:
2090 return $this->mNoIndexSet;
2091
2092 case ParserOutputFlags::NEW_SECTION:
2093 return $this->getNewSection();
2094
2095 case ParserOutputFlags::HIDE_NEW_SECTION:
2096 return $this->getHideNewSection();
2097
2098 case ParserOutputFlags::PREVENT_CLICKJACKING:
2099 return $this->getPreventClickjacking();
2100
2101 default:
2102 return $this->mFlags[$name] ?? false;
2103
2104 }
2105 }
2106
2118 public function appendOutputStrings( string|ParserOutputStringSets $name, array $value ): void {
2119 if ( is_string( $name ) ) {
2120 $name = ParserOutputStringSets::from( $name );
2121 }
2122 match ( $name ) {
2123 ParserOutputStringSets::MODULE =>
2124 $this->addModules( $value ),
2125 ParserOutputStringSets::MODULE_STYLE =>
2126 $this->addModuleStyles( $value ),
2127 ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC =>
2128 array_walk( $value, fn ( $v, $i ) =>
2129 $this->addExtraCSPDefaultSrc( $v )
2130 ),
2131 ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC =>
2132 array_walk( $value, fn ( $v, $i ) =>
2133 $this->addExtraCSPScriptSrc( $v )
2134 ),
2135 ParserOutputStringSets::EXTRA_CSP_STYLE_SRC =>
2136 array_walk( $value, fn ( $v, $i ) =>
2137 $this->addExtraCSPStyleSrc( $v )
2138 ),
2139 };
2140 }
2141
2154 public function getOutputStrings( string|ParserOutputStringSets $name ): array {
2155 if ( is_string( $name ) ) {
2156 $name = ParserOutputStringSets::from( $name );
2157 }
2158 return match ( $name ) {
2159 ParserOutputStringSets::MODULE =>
2160 $this->getModules(),
2161 ParserOutputStringSets::MODULE_STYLE =>
2162 $this->getModuleStyles(),
2163 ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC =>
2164 $this->getExtraCSPDefaultSrcs(),
2165 ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC =>
2166 $this->getExtraCSPScriptSrcs(),
2167 ParserOutputStringSets::EXTRA_CSP_STYLE_SRC =>
2168 $this->getExtraCSPStyleSrcs(),
2169 };
2170 }
2171
2221 public function setExtensionData( $key, $value ): void {
2222 if (
2223 array_key_exists( $key, $this->mExtensionData ) &&
2224 $this->mExtensionData[$key] !== $value
2225 ) {
2226 // This behavior was deprecated in 1.38. We will eventually
2227 // emit a warning here, then throw an exception.
2228 }
2229 if ( $value === null ) {
2230 unset( $this->mExtensionData[$key] );
2231 } else {
2232 $this->mExtensionData[$key] = $value;
2233 }
2234 }
2235
2260 public function appendExtensionData(
2261 string $key,
2262 $value,
2263 MergeStrategy|string $strategy = MergeStrategy::UNION
2264 ): void {
2265 if ( is_string( $strategy ) ) {
2266 $strategy = MergeStrategy::from( $strategy );
2267 }
2268 $this->mExtensionData = self::mergeMapStrategy(
2269 $this->mExtensionData,
2270 [ $key => self::makeMapStrategy( $value, $strategy ) ]
2271 );
2272 }
2273
2285 public function getExtensionData( $key ) {
2286 $value = $this->mExtensionData[$key] ?? null;
2287 if ( is_array( $value ) ) {
2288 if ( ( $value[self::MW_MERGE_STRATEGY_KEY] ?? null ) === MergeStrategy::SUM->value ) {
2289 return $value['value'];
2290 }
2291 // Don't expose our internal merge strategy key.
2292 unset( $value[self::MW_MERGE_STRATEGY_KEY] );
2293 }
2294 return $value;
2295 }
2296
2297 private static function getTimes( ?string $clock = null ): array {
2298 $ret = [];
2299 if ( !$clock || $clock === 'wall' ) {
2300 $ret['wall'] = hrtime( true ) / 10 ** 9;
2301 }
2302 if ( !$clock || $clock === 'cpu' ) {
2303 $ru = getrusage( 0 /* RUSAGE_SELF */ );
2304 $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
2305 $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
2306 }
2307 return $ret;
2308 }
2309
2316 public function resetParseStartTime(): void {
2317 $this->mParseStartTime = self::getTimes();
2318 $this->mTimeProfile = [];
2319 }
2320
2330 public function clearParseStartTime(): void {
2331 $this->mParseStartTime = [];
2332 }
2333
2344 public function recordTimeProfile() {
2345 if ( !$this->mParseStartTime ) {
2346 // If resetParseStartTime was never called, there is nothing to record
2347 return;
2348 }
2349
2350 if ( $this->mTimeProfile !== [] ) {
2351 // Don't override the times recorded by the previous call to recordTimeProfile().
2352 return;
2353 }
2354
2355 $now = self::getTimes();
2356 $this->mTimeProfile = [
2357 'wall' => $now['wall'] - $this->mParseStartTime['wall'],
2358 'cpu' => $now['cpu'] - $this->mParseStartTime['cpu'],
2359 ];
2360 }
2361
2379 public function getTimeProfile( string $clock ) {
2380 return $this->mTimeProfile[ $clock ] ?? null;
2381 }
2382
2402 public function setLimitReportData( $key, $value ): void {
2403 $this->mLimitReportData[$key] = $value;
2404
2405 if ( is_array( $value ) ) {
2406 if ( array_keys( $value ) === [ 0, 1 ]
2407 && is_numeric( $value[0] )
2408 && is_numeric( $value[1] )
2409 ) {
2410 $data = [ 'value' => $value[0], 'limit' => $value[1] ];
2411 } else {
2412 $data = $value;
2413 }
2414 } else {
2415 $data = $value;
2416 }
2417
2418 if ( strpos( $key, '-' ) ) {
2419 [ $ns, $name ] = explode( '-', $key, 2 );
2420 $this->mLimitReportJSData[$ns][$name] = $data;
2421 } else {
2422 $this->mLimitReportJSData[$key] = $data;
2423 }
2424 }
2425
2442 public function hasReducedExpiry(): bool {
2443 if ( $this->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) ) {
2444 // If this page has async content, then we should re-run
2445 // RefreshLinksJob whenever we regenerate the page.
2446 return true;
2447 }
2448 $parserCacheExpireTime = MediaWikiServices::getInstance()->getMainConfig()->get(
2449 MainConfigNames::ParserCacheExpireTime );
2450
2451 return $this->getCacheExpiry() < $parserCacheExpireTime;
2452 }
2453
2454 public function getCacheExpiry(): int {
2455 $expiry = parent::getCacheExpiry();
2456 if ( $this->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY ) ) {
2457 $asyncExpireTime = MediaWikiServices::getInstance()->getMainConfig()->get(
2458 MainConfigNames::ParserCacheAsyncExpireTime
2459 );
2460 $expiry = min( $expiry, $asyncExpireTime );
2461 }
2462 return $expiry;
2463 }
2464
2477 public function setPreventClickjacking( bool $flag ): void {
2478 $this->mPreventClickjacking = $flag;
2479 }
2480
2488 public function getPreventClickjacking(): bool {
2489 return $this->mPreventClickjacking;
2490 }
2491
2498 public function updateRuntimeAdaptiveExpiry( int $ttl ): void {
2499 $this->mMaxAdaptiveExpiry ??= $ttl;
2500 $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
2501 $this->updateCacheExpiry( $ttl );
2502 }
2503
2513 public function addExtraCSPDefaultSrc( $src ): void {
2514 $this->mExtraDefaultSrcs[] = $src;
2515 }
2516
2523 public function addExtraCSPStyleSrc( $src ): void {
2524 $this->mExtraStyleSrcs[] = $src;
2525 }
2526
2535 public function addExtraCSPScriptSrc( $src ): void {
2536 $this->mExtraScriptSrcs[] = $src;
2537 }
2538
2544 public function finalizeAdaptiveCacheExpiry(): void {
2545 if ( $this->mMaxAdaptiveExpiry === null ) {
2546 return; // not set
2547 }
2548
2549 $runtime = $this->getTimeProfile( 'wall' );
2550 if ( is_float( $runtime ) ) {
2551 $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
2552 / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
2553 // SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
2554 $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
2555
2556 $adaptiveTTL = intval( $slope * $runtime + $point );
2557 $adaptiveTTL = max( $adaptiveTTL, self::MIN_AR_TTL );
2558 $adaptiveTTL = min( $adaptiveTTL, $this->mMaxAdaptiveExpiry );
2559 $this->updateCacheExpiry( $adaptiveTTL );
2560 }
2561 }
2562
2567 public function setFromParserOptions( ParserOptions $parserOptions ) {
2568 // Copied from Parser.php::parse and should probably be abstracted
2569 // into the parent base class (probably as part of T236809)
2570 // Wrap non-interface parser output in a <div> so it can be targeted
2571 // with CSS (T37247)
2572 $class = $parserOptions->getWrapOutputClass();
2573 if ( $class !== false && !$parserOptions->isMessage() ) {
2574 $this->addWrapperDivClass( $class );
2575 }
2576
2577 // Record whether we should suppress section edit links
2578 if ( $parserOptions->getSuppressSectionEditLinks() ) {
2579 $this->setOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS );
2580 }
2581
2582 // Record whether we should wrap sections for collapsing them
2583 if ( $parserOptions->getCollapsibleSections() ) {
2584 $this->setOutputFlag( ParserOutputFlags::COLLAPSIBLE_SECTIONS );
2585 }
2586
2587 // Record whether this is a preview parse in the output (T341010)
2588 if ( $parserOptions->getIsPreview() ) {
2589 $this->setOutputFlag( ParserOutputFlags::IS_PREVIEW, true );
2590 // Ensure that previews aren't cacheable, just to be safe.
2591 $this->updateCacheExpiry( 0 );
2592 }
2593
2594 // Record whether this was parsed with the legacy parser
2595 // (Unlike some other options here, this does/should fork the cache.)
2596 if ( $parserOptions->getUseParsoid() ) {
2597 $this->setOutputFlag( ParserOutputFlags::USE_PARSOID, true );
2598 }
2599 }
2600
2607 $this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
2608 $this->mWarningMsgs = self::mergeMap( $this->mWarningMsgs, $source->mWarningMsgs );
2609 $this->mTimestamp = $this->useMaxValue( $this->mTimestamp, $source->getRevisionTimestamp() );
2610 if ( $source->hasCacheTime() ) {
2611 $sourceCacheTime = $source->getCacheTime();
2612 if (
2613 !$this->hasCacheTime() ||
2614 // "undocumented use of -1 to mean not cacheable"
2615 // deprecated, but still supported by ::setCacheTime()
2616 strval( $sourceCacheTime ) === '-1' ||
2617 (
2618 strval( $this->getCacheTime() ) !== '-1' &&
2619 // use newer of the two times
2620 $this->getCacheTime() < $sourceCacheTime
2621 )
2622 ) {
2623 $this->setCacheTime( $sourceCacheTime );
2624 }
2625 }
2626 if ( $source->getRenderId() !== null ) {
2627 // Final render ID should be a function of all component POs
2628 $rid = ( $this->getRenderId() ?? '' ) . $source->getRenderId();
2629 $this->setRenderId( $rid );
2630 }
2631 if ( $source->getCacheRevisionId() !== null ) {
2632 $sourceCacheRevisionId = $source->getCacheRevisionId();
2633 $thisCacheRevisionId = $this->getCacheRevisionId();
2634 if ( $thisCacheRevisionId === null ) {
2635 $this->setCacheRevisionId( $sourceCacheRevisionId );
2636 } elseif ( $sourceCacheRevisionId !== $thisCacheRevisionId ) {
2637 // May throw an exception here in the future
2639 __METHOD__ . ": conflicting revision IDs " .
2640 "$thisCacheRevisionId and $sourceCacheRevisionId"
2641 );
2642 }
2643 }
2644
2645 foreach ( self::SPECULATIVE_FIELDS as $field ) {
2646 if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
2647 wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
2648 }
2649 $this->$field = $this->useMaxValue( $this->$field, $source->$field );
2650 }
2651
2652 $this->mParseStartTime = $this->useEachMinValue(
2653 $this->mParseStartTime,
2654 $source->mParseStartTime
2655 );
2656
2657 $this->mTimeProfile = $this->useEachTotalValue(
2658 $this->mTimeProfile,
2659 $source->mTimeProfile
2660 );
2661
2662 $this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
2663 $this->mParseUsedOptions = self::mergeMap( $this->mParseUsedOptions, $source->mParseUsedOptions );
2664
2665 // TODO: maintain per-slot limit reports!
2666 if ( !$this->mLimitReportData ) {
2667 $this->mLimitReportData = $source->mLimitReportData;
2668 }
2669 if ( !$this->mLimitReportJSData ) {
2670 $this->mLimitReportJSData = $source->mLimitReportJSData;
2671 }
2672 }
2673
2679 public function mergeHtmlMetaDataFrom( ParserOutput $source ): void {
2680 // HTML and HTTP
2681 $this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
2682 $this->addModules( $source->getModules() );
2683 $this->addModuleStyles( $source->getModuleStyles() );
2684 $this->mJsConfigVars = self::mergeMapStrategy( $this->mJsConfigVars, $source->mJsConfigVars );
2685 if ( $source->mMaxAdaptiveExpiry !== null ) {
2686 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
2687 $this->updateRuntimeAdaptiveExpiry( $source->mMaxAdaptiveExpiry );
2688 }
2689 $this->mExtraStyleSrcs = self::mergeList(
2690 $this->mExtraStyleSrcs,
2691 $source->getExtraCSPStyleSrcs()
2692 );
2693 $this->mExtraScriptSrcs = self::mergeList(
2694 $this->mExtraScriptSrcs,
2695 $source->getExtraCSPScriptSrcs()
2696 );
2697 $this->mExtraDefaultSrcs = self::mergeList(
2698 $this->mExtraDefaultSrcs,
2699 $source->getExtraCSPDefaultSrcs()
2700 );
2701
2702 // "noindex" always wins!
2703 $this->mIndexSet = $this->mIndexSet || $source->mIndexSet;
2704 $this->mNoIndexSet = $this->mNoIndexSet || $source->mNoIndexSet;
2705
2706 // Skin control
2707 $this->mNewSection = $this->mNewSection || $source->getNewSection();
2708 $this->mHideNewSection = $this->mHideNewSection || $source->getHideNewSection();
2709 $this->mNoGallery = $this->mNoGallery || $source->getNoGallery();
2710 $this->mEnableOOUI = $this->mEnableOOUI || $source->getEnableOOUI();
2711 $this->mPreventClickjacking = $this->mPreventClickjacking || $source->getPreventClickjacking();
2712
2713 $tocData = $this->getTOCData();
2714 $sourceTocData = $source->getTOCData();
2715 if ( $tocData !== null ) {
2716 if ( $sourceTocData !== null ) {
2717 // T327429: Section merging is broken, since it doesn't respect
2718 // global numbering, but there are tests which expect section
2719 // metadata to be concatenated.
2720 // There should eventually be a deprecation warning here.
2721 foreach ( $sourceTocData->getSections() as $s ) {
2722 $tocData->addSection( $s );
2723 }
2724 }
2725 } elseif ( $sourceTocData !== null ) {
2726 $this->setTOCData( $sourceTocData );
2727 }
2728
2729 // XXX: we don't want to concatenate title text, so first write wins.
2730 // We should use the first *modified* title text, but we don't have the original to check.
2731 if ( $this->mTitleText === '' ) {
2732 $this->mTitleText = $source->mTitleText;
2733 }
2734
2735 // class names are stored in array keys
2736 $this->mWrapperDivClasses = self::mergeMap(
2737 $this->mWrapperDivClasses,
2738 $source->mWrapperDivClasses
2739 );
2740
2741 // NOTE: last write wins, same as within one ParserOutput
2742 $this->mIndicators = self::mergeMap( $this->mIndicators, $source->getIndicators() );
2743
2744 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
2745 // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
2746 // kinds of extension data to be merged in different ways.
2747 $this->mExtensionData = self::mergeMapStrategy(
2748 $this->mExtensionData,
2749 $source->mExtensionData
2750 );
2751
2752 if ( $source->mCacheExpiry !== null ) {
2753 $this->updateCacheExpiry( $source->mCacheExpiry );
2754 }
2755 }
2756
2763 foreach ( ParserOutputLinkTypes::cases() as $linkType ) {
2764 foreach ( $source->getLinkList( $linkType ) as $linkItem ) {
2765 $this->appendLinkList( $linkType, $linkItem );
2766 }
2767 }
2768 $this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
2769
2770 // TODO: add a $mergeStrategy parameter to setPageProperty to allow different
2771 // kinds of properties to be merged in different ways.
2772 // (Model this after ::appendJsConfigVar(); use ::mergeMapStrategy here)
2773 $this->mProperties = self::mergeMap( $this->mProperties, $source->getPageProperties() );
2774
2775 // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
2776 $this->mExtensionData = self::mergeMapStrategy(
2777 $this->mExtensionData,
2778 $source->mExtensionData
2779 );
2780 }
2781
2791 public function collectMetadata( ContentMetadataCollector $metadata ): void {
2792 // Uniform handling of all boolean flags: they are OR'ed together.
2793 $flags = array_keys(
2794 $this->mFlags + array_flip( ParserOutputFlags::values() )
2795 );
2796 foreach ( $flags as $name ) {
2797 $name = (string)$name;
2798 if ( $this->getOutputFlag( $name ) ) {
2799 $metadata->setOutputFlag( $name );
2800 }
2801 }
2802
2803 // Uniform handling of string sets: they are unioned.
2804 // (This includes modules, style modes, and CSP src.)
2805 foreach ( ParserOutputStringSets::values() as $name ) {
2806 $metadata->appendOutputStrings(
2807 $name, $this->getOutputStrings( $name )
2808 );
2809 }
2810
2811 foreach ( $this->mCategories as $cat => $key ) {
2812 // Numeric category strings are going to come out of the
2813 // `mCategories` array as ints; cast back to string.
2814 // Also convert back to a LinkTarget!
2815 $lt = TitleValue::tryNew( NS_CATEGORY, (string)$cat );
2816 $metadata->addCategory( $lt, $key );
2817 }
2818
2819 foreach ( $this->mLinks as $ns => $arr ) {
2820 foreach ( $arr as $dbk => $id ) {
2821 // Numeric titles are going to come out of the
2822 // `mLinks` array as ints; cast back to string.
2823 $lt = TitleValue::tryNew( $ns, (string)$dbk );
2824 $metadata->addLink( $lt, $id );
2825 }
2826 }
2827
2828 foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
2829 foreach ( $arr as $dbk => $ignore ) {
2830 $lt = TitleValue::tryNew( NS_MAIN, (string)$dbk, '', $prefix );
2831 $metadata->addLink( $lt );
2832 }
2833 }
2834
2835 foreach ( $this->mLinksSpecial as $dbk => $ignore ) {
2836 // Numeric titles are going to come out of the
2837 // `mLinksSpecial` array as ints; cast back to string.
2838 $lt = TitleValue::tryNew( NS_SPECIAL, (string)$dbk );
2839 $metadata->addLink( $lt );
2840 }
2841
2842 foreach ( $this->mImages as $name => $ignore ) {
2843 // Numeric titles come out of mImages as ints.
2844 $lt = TitleValue::tryNew( NS_FILE, (string)$name );
2845 $props = $this->mFileSearchOptions[$name] ?? [];
2846 $metadata->addImage( $lt, $props['time'] ?? null, $props['sha1'] ?? null );
2847 }
2848
2849 foreach ( $this->mLanguageLinkMap as $lang => $title ) {
2850 # language links can have fragments!
2851 [ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
2852 $lt = TitleValue::tryNew( NS_MAIN, $title, $frag, (string)$lang );
2853 $metadata->addLanguageLink( $lt );
2854 }
2855
2856 foreach ( $this->mJsConfigVars as $key => $value ) {
2857 // Numeric keys and items are going to come out of the
2858 // `mJsConfigVars` array as ints; cast back to string.
2859 $key = (string)$key;
2860 if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
2861 self::collectMapStrategy( $value, static fn ( $v, $s ) =>
2862 $metadata->appendJsConfigVar( $key, $v, $s )
2863 );
2864 } elseif ( $metadata instanceof ParserOutput &&
2865 array_key_exists( $key, $metadata->mJsConfigVars )
2866 ) {
2867 // This behavior is deprecated, will likely result in
2868 // incorrect output, and we'll eventually emit a
2869 // warning here---but at the moment this is usually
2870 // caused by limitations in Parsoid and/or use of
2871 // the ParserAfterParse hook: T303015#7770480
2872 $metadata->mJsConfigVars[$key] = $value;
2873 } else {
2874 $metadata->setJsConfigVar( $key, $value );
2875 }
2876 }
2877 foreach ( $this->mExtensionData as $key => $value ) {
2878 // Numeric keys and items are going to come out of the array as
2879 // ints, cast back to string.
2880 $key = (string)$key;
2881 if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
2882 self::collectMapStrategy( $value, static fn ( $v, $s ) =>
2883 $metadata->appendExtensionData( $key, $v, $s )
2884 );
2885 } elseif ( $metadata instanceof ParserOutput &&
2886 array_key_exists( $key, $metadata->mExtensionData )
2887 ) {
2888 // This behavior is deprecated, will likely result in
2889 // incorrect output, and we'll eventually emit a
2890 // warning here---but at the moment this is usually
2891 // caused by limitations in Parsoid and/or use of
2892 // the ParserAfterParse hook: T303015#7770480
2893 $metadata->mExtensionData[$key] = $value;
2894 } else {
2895 $metadata->setExtensionData( $key, $value );
2896 }
2897 }
2898 foreach ( $this->mExternalLinks as $url => $ignore ) {
2899 $metadata->addExternalLink( (string)$url );
2900 }
2901 foreach ( $this->mProperties as $prop => $value ) {
2902 // Numeric properties are going to come out of the array as ints
2903 $prop = (string)$prop;
2904 if ( is_string( $value ) ) {
2905 $metadata->setUnsortedPageProperty( $prop, $value );
2906 } elseif ( is_numeric( $value ) ) {
2907 $metadata->setNumericPageProperty( $prop, $value );
2908 } else {
2909 // Deprecated, but there are still sites which call
2910 // ::setPageProperty() with "unusual" values (T374046)
2911 wfDeprecated( __METHOD__ . ' with unusual page property', '1.45' );
2912 }
2913 }
2914 foreach ( $this->mLimitReportData as $key => $value ) {
2915 $metadata->setLimitReportData( (string)$key, $value );
2916 }
2917 foreach ( $this->mIndicators as $id => $content ) {
2918 $metadata->setIndicator( (string)$id, $content );
2919 }
2920
2921 // ParserOutput-only fields; maintained "behind the curtain"
2922 // since Parsoid doesn't have to know about them.
2923 //
2924 // In production use, the $metadata supplied to this method
2925 // will almost always be an instance of ParserOutput, passed to
2926 // Parsoid by core when parsing begins and returned to core by
2927 // Parsoid as a ContentMetadataCollector (Parsoid's name for
2928 // ParserOutput) when DataAccess::parseWikitext() is called.
2929 //
2930 // We may use still Parsoid's StubMetadataCollector for testing or
2931 // when running Parsoid in standalone mode, so forcing a downcast
2932 // here would lose some flexibility.
2933
2934 if ( $metadata instanceof ParserOutput ) {
2935 foreach ( $this->getUsedOptions() as $opt ) {
2936 $metadata->recordOption( $opt );
2937 }
2938 if ( $this->mMaxAdaptiveExpiry !== null ) {
2939 $metadata->updateRuntimeAdaptiveExpiry( $this->mMaxAdaptiveExpiry );
2940 }
2941 if ( $this->mCacheExpiry !== null ) {
2942 $metadata->updateCacheExpiry( $this->mCacheExpiry );
2943 }
2944 if ( $this->mCacheTime !== '' ) {
2945 $metadata->setCacheTime( $this->mCacheTime );
2946 }
2947 if ( $this->mCacheRevisionId !== null ) {
2948 $metadata->setCacheRevisionId( $this->mCacheRevisionId );
2949 }
2950 // T293514: We should use the first *modified* title text, but
2951 // we don't have the original to check.
2952 $otherTitle = $metadata->getTitleText();
2953 if ( $otherTitle === '' ) {
2954 $metadata->setTitleText( $this->getTitleText() );
2955 }
2956 foreach (
2957 [
2958 ParserOutputLinkTypes::TEMPLATE,
2959 ParserOutputLinkTypes::EXISTENCE,
2960 ] as $linkType ) {
2961 foreach ( $this->getLinkList( $linkType ) as $linkItem ) {
2962 $metadata->appendLinkList( $linkType, $linkItem );
2963 }
2964 }
2965 foreach ( $this->mWarningMsgs as $key => $msg ) {
2966 $metadata->addWarningMsgVal( $msg, (string)$key );
2967 }
2968 }
2969 }
2970
2971 private static function mergeMixedList( array $a, array $b ): array {
2972 return array_unique( array_merge( $a, $b ), SORT_REGULAR );
2973 }
2974
2975 private static function mergeList( array $a, array $b ): array {
2976 return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
2977 }
2978
2979 private static function mergeMap( array $a, array $b ): array {
2980 return array_replace( $a, $b );
2981 }
2982
2986 private static function makeMapStrategy( string|int $value, MergeStrategy $strategy ): array {
2987 $base = [ self::MW_MERGE_STRATEGY_KEY => $strategy->value ];
2988 switch ( $strategy ) {
2989 case MergeStrategy::UNION:
2990 return [ $value => true, ...$base ];
2991 case MergeStrategy::SUM:
2992 Assert::parameterType( 'integer', $value, '$value' );
2993 return [ 'value' => $value, ...$base ];
2994 default:
2995 throw new InvalidArgumentException( "Unknown merge strategy {$strategy->value}" );
2996 }
2997 }
2998
3007 private static function collectMapStrategy( array $map, callable $f ): void {
3008 $strategy = MergeStrategy::from(
3009 $map[self::MW_MERGE_STRATEGY_KEY]
3010 );
3011 foreach ( $map as $key => $value ) {
3012 if ( $key === self::MW_MERGE_STRATEGY_KEY ) {
3013 continue;
3014 }
3015 switch ( $strategy ) {
3016 case MergeStrategy::UNION:
3017 $f( $key, $strategy ); // ignore value
3018 break;
3019 case MergeStrategy::SUM:
3020 $f( $value, $strategy ); // ignore key
3021 break;
3022 }
3023 }
3024 }
3025
3026 private static function mergeMapStrategy( array $a, array $b ): array {
3027 foreach ( $b as $key => $bValue ) {
3028 if ( !array_key_exists( $key, $a ) ) {
3029 $a[$key] = $bValue;
3030 } elseif (
3031 is_array( $a[$key] ) &&
3032 isset( $a[$key][self::MW_MERGE_STRATEGY_KEY] ) &&
3033 isset( $bValue[self::MW_MERGE_STRATEGY_KEY] )
3034 ) {
3035 $strategy = $bValue[self::MW_MERGE_STRATEGY_KEY];
3036 if ( $strategy !== $a[$key][self::MW_MERGE_STRATEGY_KEY] ) {
3037 throw new InvalidArgumentException( "Conflicting merge strategy for $key" );
3038 }
3039 $strategy = MergeStrategy::from( $strategy );
3040 switch ( $strategy ) {
3041 case MergeStrategy::UNION:
3042 // Note the array_merge is *not* safe to use here, because
3043 // the $bValue is expected to be a map from items to `true`.
3044 // If the item is a numeric string like '1' then array_merge
3045 // will convert it to an integer and renumber the array!
3046 $a[$key] = array_replace( $a[$key], $bValue );
3047 break;
3048 case MergeStrategy::SUM:
3049 $a[$key]['value'] += $b[$key]['value'];
3050 break;
3051 default:
3052 throw new InvalidArgumentException( "Unknown merge strategy {$strategy->value}" );
3053 }
3054 } else {
3055 $valuesSame = ( $a[$key] === $bValue );
3056 if ( ( !$valuesSame ) &&
3057 is_object( $a[$key] ) &&
3058 is_object( $bValue )
3059 ) {
3060 $jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
3061 $valuesSame = ( $jsonCodec->toJsonArray( $a[$key] ) === $jsonCodec->toJsonArray( $bValue ) );
3062 }
3063 if ( !$valuesSame ) {
3064 // Silently replace for now; in the future will first emit
3065 // a deprecation warning, and then (later) throw.
3066 $a[$key] = $bValue;
3067 }
3068 }
3069 }
3070 return $a;
3071 }
3072
3073 private static function useEachMinValue( array $a, array $b ): array {
3074 $values = [];
3075 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
3076
3077 foreach ( $keys as $k ) {
3078 $values[$k] = min( $a[$k] ?? INF, $b[$k] ?? INF );
3079 }
3080
3081 return $values;
3082 }
3083
3084 private static function useEachTotalValue( array $a, array $b ): array {
3085 $values = [];
3086 $keys = array_merge( array_keys( $a ), array_keys( $b ) );
3087
3088 foreach ( $keys as $k ) {
3089 $values[$k] = ( $a[$k] ?? 0 ) + ( $b[$k] ?? 0 );
3090 }
3091
3092 return $values;
3093 }
3094
3100 private static function useMaxValue( $a, $b ) {
3101 if ( $a === null ) {
3102 return $b;
3103 }
3104
3105 if ( $b === null ) {
3106 return $a;
3107 }
3108
3109 return max( $a, $b );
3110 }
3111
3118 public function toJsonArray(): array {
3119 // WARNING: When changing how this class is serialized, follow the instructions
3120 // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
3121 $data = [
3122 'Text' => $this->hasText() ? $this->getContentHolderText() : null,
3123 'LanguageLinks' => $this->getLanguageLinksInternal(),
3124 'Categories' => $this->mCategories,
3125 'Indicators' => $this->mIndicators,
3126 'TitleText' => $this->mTitleText,
3127 'Links' => $this->mLinks,
3128 'LinksSpecial' => $this->mLinksSpecial,
3129 'Templates' => $this->mTemplates,
3130 'TemplateIds' => $this->mTemplateIds,
3131 'Images' => $this->mImages,
3132 'FileSearchOptions' => $this->mFileSearchOptions,
3133 'ExternalLinks' => $this->mExternalLinks,
3134 'InterwikiLinks' => $this->mInterwikiLinks,
3135 'ExistenceLinks' => $this->existenceLinks,
3136 'NewSection' => $this->mNewSection,
3137 'HideNewSection' => $this->mHideNewSection,
3138 'NoGallery' => $this->mNoGallery,
3139 'HeadItems' => $this->mHeadItems,
3140 'Modules' => array_keys( $this->mModuleSet ),
3141 'ModuleStyles' => array_keys( $this->mModuleStyleSet ),
3142 'JsConfigVars' => $this->mJsConfigVars,
3143 'Warnings' => $this->mWarnings,
3144 'WarningMsgs' => $this->mWarningMsgs,
3145 'TOCData' => $this->mTOCData,
3146 'Properties' => self::detectAndEncodeBinary( $this->mProperties ),
3147 'Timestamp' => $this->mTimestamp,
3148 'EnableOOUI' => $this->mEnableOOUI,
3149 'IndexPolicy' => $this->getIndexPolicy(),
3150 // may contain arbitrary structures!
3151 'ExtensionData' => $this->mExtensionData,
3152 'LimitReportData' => $this->mLimitReportData,
3153 'LimitReportJSData' => $this->mLimitReportJSData,
3154 'CacheMessage' => $this->mCacheMessage,
3155 'TimeProfile' => $this->mTimeProfile,
3156 'ParseStartTime' => [], // don't serialize this
3157 'PreventClickjacking' => $this->mPreventClickjacking,
3158 'ExtraScriptSrcs' => $this->mExtraScriptSrcs,
3159 'ExtraDefaultSrcs' => $this->mExtraDefaultSrcs,
3160 'ExtraStyleSrcs' => $this->mExtraStyleSrcs,
3161 'Flags' => $this->mFlags,
3162 'SpeculativeRevId' => $this->mSpeculativeRevId,
3163 'SpeculativePageIdUsed' => $this->speculativePageIdUsed,
3164 'RevisionTimestampUsed' => $this->revisionTimestampUsed,
3165 'RevisionUsedSha1Base36' => $this->revisionUsedSha1Base36,
3166 'WrapperDivClasses' => $this->mWrapperDivClasses,
3167 ];
3168 // TODO ultimately we'll change the serialization to directly
3169 // encode the ContentHolder, but let's maintain compatibility for now.
3170 if ( $this->contentHolder->isParsoidContent() ) {
3171 $pageBundle = $this->contentHolder->getBasePageBundle();
3172 $data[ 'ExtensionData' ][ self::PARSOID_PAGE_BUNDLE_KEY ] =
3173 $pageBundle->toJsonArray();
3174 }
3175
3176 // Fill in missing fields from parents. Array addition does not override existing fields.
3177 $data += parent::toJsonArray();
3178
3179 // TODO: make more fields optional!
3180
3181 if ( $this->mMaxAdaptiveExpiry !== null ) {
3182 $data['MaxAdaptiveExpiry'] = $this->mMaxAdaptiveExpiry;
3183 }
3184
3185 return $data;
3186 }
3187
3188 public static function newFromJsonArray( array $json ): ParserOutput {
3189 $parserOutput = new ParserOutput();
3190 $parserOutput->initFromJson( $json );
3191 return $parserOutput;
3192 }
3193
3198 protected function initFromJson( array $jsonData ): void {
3199 parent::initFromJson( $jsonData );
3200
3201 // WARNING: When changing how this class is serialized, follow the instructions
3202 // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
3203 // (This includes changing default values when fields are missing.)
3204
3205 $pageBundleData = $jsonData['ExtensionData'][ self::PARSOID_PAGE_BUNDLE_KEY ] ?? null;
3206 if ( $pageBundleData ) {
3207 unset( $jsonData['ExtensionData'][ self::PARSOID_PAGE_BUNDLE_KEY ] );
3208 $pb = HtmlPageBundle::newFromJsonArray(
3209 $pageBundleData + [ 'html' => $jsonData[ 'Text' ] ?? '' ]
3210 );
3211 $this->contentHolder = ContentHolder::createFromParsoidPageBundle( $pb );
3212 } else {
3213 $this->contentHolder = ContentHolder::createFromLegacyString( $jsonData[ 'Text' ] ?? '' );
3214 }
3215 if ( !isset( $jsonData['Text'] ) ) {
3216 // Make the content holder empty if 'Text' was null.
3217 $this->contentHolder->setAsHtmlString( ContentHolder::BODY_FRAGMENT, null );
3218 }
3219
3220 $this->mLanguageLinkMap = [];
3221 foreach ( ( $jsonData['LanguageLinks'] ?? [] ) as $l ) {
3222 // T374736: old serialized parser cache entries may
3223 // contain invalid language links; drop them quietly.
3224 // (This code can be removed two LTS releases past 1.45.)
3225 if ( str_contains( $l, ':' ) ) {
3226 $this->addLanguageLink( $l );
3227 }
3228 }
3229 // Default values should match the property default values.
3230 $this->mCategories = $jsonData['Categories'] ?? [];
3231 $this->mIndicators = $jsonData['Indicators'] ?? [];
3232 $this->mTitleText = $jsonData['TitleText'] ?? '';
3233 $this->mLinks = $jsonData['Links'] ?? [];
3234 $this->mLinksSpecial = $jsonData['LinksSpecial'] ?? [];
3235 $this->mTemplates = $jsonData['Templates'] ?? [];
3236 $this->mTemplateIds = $jsonData['TemplateIds'] ?? [];
3237 $this->mImages = $jsonData['Images'] ?? [];
3238 $this->mFileSearchOptions = $jsonData['FileSearchOptions'] ?? [];
3239 $this->mExternalLinks = $jsonData['ExternalLinks'] ?? [];
3240 $this->mInterwikiLinks = $jsonData['InterwikiLinks'] ?? [];
3241 $this->existenceLinks = $jsonData['ExistenceLinks'] ?? [];
3242 $this->mHeadItems = $jsonData['HeadItems'] ?? [];
3243 $this->mModuleSet = array_fill_keys( $jsonData['Modules'] ?? [], true );
3244 $this->mModuleStyleSet = array_fill_keys( $jsonData['ModuleStyles'] ?? [], true );
3245 $this->mJsConfigVars = $jsonData['JsConfigVars'] ?? [];
3246 $this->mWarnings = $jsonData['Warnings'] ?? [];
3247 $this->mWarningMsgs = $jsonData['WarningMsgs'] ?? [];
3248
3249 // Set flags stored as properties
3250 $this->mFlags = $jsonData['Flags'] ?? [];
3251 $this->mNoGallery = $jsonData['NoGallery'] ?? false;
3252 $this->mEnableOOUI = $jsonData['EnableOOUI'] ?? false;
3253 $this->setIndexPolicy( $jsonData['IndexPolicy'] ?? '' );
3254 $this->mNewSection = $jsonData['NewSection'] ?? false;
3255 $this->mHideNewSection = $jsonData['HideNewSection'] ?? false;
3256 $this->mPreventClickjacking = $jsonData['PreventClickjacking'] ?? false;
3257 // Set all generic output flags (whether stored as properties or not)
3258 // (This is effectively a logical-OR if these are also serialized
3259 // above.)
3260 foreach ( $jsonData['OutputFlags'] ?? [] as $flag ) {
3261 $this->setOutputFlag( $flag );
3262 }
3263
3264 if ( isset( $jsonData['TOCData'] ) ) {
3265 $this->mTOCData = $jsonData['TOCData'];
3266 // Backward-compatibility with old TOCData encoding (T327439)
3267 // emitted in MW < 1.45
3268 } elseif (
3269 ( $jsonData['Sections'] ?? [] ) !== [] ||
3270 // distinguish "no sections" from "sections not set"
3271 $this->getOutputFlag( 'mw:toc-set' )
3272 ) {
3273 $this->setSections( $jsonData['Sections'] ?? [] );
3274 unset( $this->mFlags['mw:toc-set'] );
3275 if ( isset( $jsonData['TOCExtensionData'] ) ) {
3276 $tocData = $this->getTOCData(); // created by setSections() above
3277 foreach ( $jsonData['TOCExtensionData'] as $key => $value ) {
3278 $tocData->setExtensionData( (string)$key, $value );
3279 }
3280 }
3281 }
3282 // backward-compatibility: convert page properties to their
3283 // 'database representation'. We haven't permitted non-string
3284 // non-numeric values since 1.45.
3285 $this->mProperties = [];
3286 foreach (
3287 self::detectAndDecodeBinary( $jsonData['Properties'] ?? [] )
3288 as $k => $v
3289 ) {
3290 if ( is_int( $v ) || is_float( $v ) || is_string( $v ) ) {
3291 $this->mProperties[$k] = $v;
3292 } elseif ( is_bool( $v ) ) {
3293 $this->mProperties[$k] = (int)$v;
3294 } elseif ( $v === null ) {
3295 $this->mProperties[$k] = '';
3296 } elseif ( is_array( $v ) ) {
3297 $this->mProperties[$k] = 'Array';
3298 } else {
3299 $this->mProperties[$k] = strval( $v );
3300 }
3301 }
3302 $this->mTimestamp = $jsonData['Timestamp'] ?? null;
3303 $this->mExtensionData = $jsonData['ExtensionData'] ?? [];
3304 $this->mLimitReportData = $jsonData['LimitReportData'] ?? [];
3305 $this->mLimitReportJSData = $jsonData['LimitReportJSData'] ?? [];
3306 $this->mCacheMessage = $jsonData['CacheMessage'] ?? '';
3307 $this->mParseStartTime = []; // invalid after reloading
3308 $this->mTimeProfile = $jsonData['TimeProfile'] ?? [];
3309 $this->mExtraScriptSrcs = $jsonData['ExtraScriptSrcs'] ?? [];
3310 $this->mExtraDefaultSrcs = $jsonData['ExtraDefaultSrcs'] ?? [];
3311 $this->mExtraStyleSrcs = $jsonData['ExtraStyleSrcs'] ?? [];
3312 $this->mSpeculativeRevId = $jsonData['SpeculativeRevId'] ?? null;
3313 $this->speculativePageIdUsed = $jsonData['SpeculativePageIdUsed'] ?? null;
3314 $this->revisionTimestampUsed = $jsonData['RevisionTimestampUsed'] ?? null;
3315 $this->revisionUsedSha1Base36 = $jsonData['RevisionUsedSha1Base36'] ?? null;
3316 $this->mWrapperDivClasses = $jsonData['WrapperDivClasses'] ?? [];
3317 $this->mMaxAdaptiveExpiry = $jsonData['MaxAdaptiveExpiry'] ?? null;
3318 }
3319
3329 private static function detectAndEncodeBinary( array $properties ) {
3330 foreach ( $properties as $key => $value ) {
3331 if ( is_string( $value ) ) {
3332 if ( !mb_detect_encoding( $value, 'UTF-8', true ) ) {
3333 $properties[$key] = [
3334 // T313818: This key name conflicts with JsonCodec
3335 '_type_' => 'string',
3336 '_encoding_' => 'base64',
3337 '_data_' => base64_encode( $value ),
3338 ];
3339 }
3340 }
3341 }
3342
3343 return $properties;
3344 }
3345
3354 private static function detectAndDecodeBinary( array $properties ) {
3355 foreach ( $properties as $key => $value ) {
3356 if ( is_array( $value ) && isset( $value['_encoding_'] ) ) {
3357 if ( $value['_encoding_'] === 'base64' ) {
3358 $properties[$key] = base64_decode( $value['_data_'] );
3359 }
3360 }
3361 }
3362
3363 return $properties;
3364 }
3365
3366 public function __serialize(): array {
3367 // Support for PHP serialization of ParserOutput for ParserCache
3368 // was turned off in 1.39 and is not guaranteed to work.
3369 wfDeprecated( "PHP serialization of ParserOutput", "1.39" );
3370 return (array)$this;
3371 }
3372
3373 public function __clone() {
3374 // It seems that very little of this object needs to be explicitly deep-cloned
3375 // while keeping copies reasonably separated.
3376 // Most of the non-scalar properties of this object are either
3377 // - (potentially multi-nested) arrays of scalars (which get deep-cloned), or
3378 // - arrays that may contain arbitrary elements (which don't necessarily get
3379 // deep-cloned), but for which no particular care elsewhere is given to
3380 // copying their references around (e.g. mJsConfigVars).
3381 // Hence, we are not going out of our way to ensure that the references to innermost
3382 // objects that may appear in a ParserOutput are unique. If that becomes the
3383 // expectation at any point, this method will require updating as well.
3384 // The exception is TOCData (which is an object), which we clone explicitly.
3385 if ( $this->mTOCData ) {
3386 $this->mTOCData = clone $this->mTOCData;
3387 }
3388 $this->contentHolder = clone $this->contentHolder;
3389 }
3390
3397 public function getContentHolderText(): string {
3398 $html = $this->contentHolder->getAsHtmlString( ContentHolder::BODY_FRAGMENT );
3399 if ( $html === null ) {
3400 throw new LogicException( 'This ParserOutput contains no text!' );
3401 }
3402 return $html;
3403 }
3404
3413 public function setContentHolderText( ?string $text ): void {
3414 $this->contentHolder->setAsHtmlString( ContentHolder::BODY_FRAGMENT, $text );
3415 }
3416}
3417
3419class_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.
setExtensionData( $extensionData)
Set extension data for this result.
getExtensionData()
Get the extension data as: augmentor name => data.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Represents the identity of a specific rendering of a specific revision at some point in time.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
This is one of the Core classes and should be read at least once by any new developers.
getModules( $filter=false,... $args)
Get the list of modules to include on this page.
getJsConfigVars()
Get the javascript config vars to include on this page.
getModuleStyles( $filter=false,... $args)
Get the list of style-only modules to load on this page.
Parser cache specific expiry check.
Definition CacheTime.php:24
static createFromLegacyString(string $html)
Create a ContentHolder from a legacy body HTML string, typically returned by the legacy parser.
static createEmpty()
Creates an empty ContentHolder that can be used as a placeholder.
Set options of the Parser.
getWrapOutputClass()
Class to use to wrap output from Parser::parse()
getIsPreview()
Parsing the page for a "preview" operation?
getUseParsoid()
Parsoid-format HTML output, or legacy wikitext parser HTML?
getCollapsibleSections()
Should section contents be wrapped in.
getSuppressSectionEditLinks()
Should section edit links be suppressed? Used when parsing wikitext which will be presented in a non-...
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.
getJsConfigVars(bool $showStrategyKeys=false)
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
setEnableOOUI(bool $enable=false)
Enables OOUI, if true, in any OutputPage instance this ParserOutput object is added to.
toJsonArray()
Returns a JSON serializable structure representing this ParserOutput instance.
unsetPageProperty(string $name)
Remove a page property.
getTimeProfile(string $clock)
Returns the time that elapsed between the most recent call to resetParseStartTime() and the first cal...
appendExtensionData(string $key, $value, MergeStrategy|string $strategy=MergeStrategy::UNION)
Appends arbitrary data to this ParserObject.
addImage( $name, $timestamp=null, $sha1=null)
Register a file dependency for this output.
setNumericPageProperty(string $propName, $numericValue)
Set a numeric page property whose value is intended to be sorted and indexed.
runOutputPipeline(ParserOptions $popts, array $options=[])
appendJsConfigVar(string $key, $value, MergeStrategy|string $strategy=MergeStrategy::UNION)
Append a value to a variable to be set in mw.config in JavaScript.
getRenderId()
Return the unique rendering id for this ParserOutput.
addTemplate( $link, $page_id, $rev_id)
Register a template dependency for this output.
addExtraCSPDefaultSrc( $src)
Add an extra value to Content-Security-Policy default-src directive.
setExtensionData( $key, $value)
Attaches arbitrary data to this ParserObject.
setRenderId(string $renderId)
Store a unique rendering id for this ParserOutput.
addOutputPageMetadata(OutputPage $out)
Accommodate very basic transcluding of a temporary OutputPage object into parser output.
addWarningMsgVal(MessageSpecifier $mv, ?string $key=null)
Add a warning to the output for this page.
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.
updateRuntimeAdaptiveExpiry(int $ttl)
Lower the runtime adaptive TTL to at most this value.
setPreventClickjacking(bool $flag)
Set the prevent-clickjacking flag.
addExtraCSPScriptSrc( $src)
Add an extra value to Content-Security-Policy script-src directive.
getExtraCSPStyleSrcs()
Get extra Content-Security-Policy 'style-src' directives.
getCategorySortKey(string $name)
Return the sort key for a given category name, or null if the category is not present in this ParserO...
getWrapperDivClass()
Returns the class (or classes) to be used with the wrapper div for this output.
const MW_MERGE_STRATEGY_UNION
Merge strategy to use for ParserOutput accumulators: "union" means that values are strings,...
setRawText(?string $text)
Set the raw text of the ParserOutput.
hasReducedExpiry()
Check whether the cache TTL was lowered from the site default.
getLinkList(string|ParserOutputLinkTypes $linkType, ?int $onlyNamespace=null)
Get a list of links of the given type.
addLanguageLink( $t)
Add a language link.
hasImages()
Return true if there are image dependencies registered for this ParserOutput.
addWarningMsg(string $msg,... $args)
Add a warning to the output for this page.
hasLinks()
Return true if the given parser output has local links registered in the metadata.
mergeHtmlMetaDataFrom(ParserOutput $source)
Merges HTML metadata such as head items, JS config vars, and HTTP cache control info from $source int...
recordTimeProfile()
Record the time since resetParseStartTime() was last called.
appendLinkList(string|ParserOutputLinkTypes $linkType, array $linkItem)
Append a link of the given type.
getDisplayTitle()
Get the title to be used for display.
setCategories(array $c)
Overwrite the category map.
getPageProperty(string $name)
Look up a page property.
setContentHolderText(?string $text)
Sets the body fragment text of the ParserOutput.
finalizeAdaptiveCacheExpiry()
Call this when parsing is done to lower the TTL based on low parse times.
getOutputFlag(ParserOutputFlags|string $name)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
getCategoryNames()
Return the names of the categories on this page.
mergeTrackingMetaDataFrom(ParserOutput $source)
Merges dependency tracking metadata such as backlinks, images used, and extension data from $source i...
getLanguage()
Get the primary language code of the output.
setSections(array $sectionArray)
__construct(?string $text=null, array $languageLinks=[], array $categoryLinks=[], $unused=false, string $titletext='')
addExtraCSPStyleSrc( $src)
Add an extra value to Content-Security-Policy style-src directive.
static newFromJsonArray(array $json)
mergeInternalMetaDataFrom(ParserOutput $source)
Merges internal metadata such as flags, accessed options, and profiling info from $source into this P...
addCacheMessage(string $msg)
Adds a comment notice about cache state to the text of the page.
getContentHolder()
Return the ContentHolder storing the HTML/DOM contents of this ParserOutput.
setLanguage(Bcp47Code $lang)
Set the primary language of the output.
getCategoryMap()
Return category names and sort keys as a map.
hasText()
Returns true if text was passed to the constructor, or set using setText().
setContentHolder(ContentHolder $contentHolder)
addHeadItem( $section, $tag=false)
Add some text to the "<head>".
getExtraCSPScriptSrcs()
Get extra Content-Security-Policy 'script-src' directives.
clearParseStartTime()
Unset the parse start time.
resetParseStartTime()
Resets the parse start timestamps for future calls to getTimeProfile() and recordTimeProfile().
setText( $text)
Set the raw text of the ParserOutput.
getExtensionData( $key)
Gets extensions data previously attached to this ParserOutput using setExtensionData().
getCacheExpiry()
Returns the number of seconds after which this object should expire.
getRedirectHeader()
Return an HTML prefix to be applied on redirect pages, or null if this is not a redirect.
setFromParserOptions(ParserOptions $parserOptions)
Transfer parser options which affect post-processing from ParserOptions to this ParserOutput.
getRawText()
Get the cacheable text with <mw:editsection> markers still in it.
setOutputFlag(ParserOutputFlags|string $name, bool $val=true)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
initFromJson(array $jsonData)
Initialize member fields from an array returned by jsonSerialize().
collectMetadata(ContentMetadataCollector $metadata)
Adds the metadata collected in this ParserOutput to the supplied ContentMetadataCollector.
appendOutputStrings(string|ParserOutputStringSets $name, array $value)
Provides a uniform interface to various string sets stored in the ParserOutput.
addWrapperDivClass( $class)
Add a CSS class to use for the wrapping div.
static isLinkInternal( $internal, $url)
Checks, if a url is pointing to the own server.
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.
Value object representing a message for i18n.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'sodipodi'=> ' $path/sodipodi -z -w $width -f $input -e $output', 'inkscape'=> ' $path/inkscape -z -w $width -f $input -e $output', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'imgserv'=> ' $path/imgserv-wrapper -i svg -o png -w$width $input $output', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> false, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], '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'=> 'SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], '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, 'UseLegacyMediaStyles' => 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, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'BlockTargetMigrationStage' => 768, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => 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, ], '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, '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' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => 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, '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, '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, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, '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: ], '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' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], '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, ], '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, '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\\JobQueue\\Jobs\\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', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'BlockTargetMigrationStage' => 'integer', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ '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', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], '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.', ],]
getKey()
Returns the message key.
$source