140 use DeprecationHelper;
142 # Flags for Parser::setFunctionHook
146 # Constants needed for external link processing
160 private const EXT_LINK_ADDR =
'(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
163 private const EXT_IMAGE_REGEX =
'/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
164 \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)avif|gif|jpg|jpeg|png|svg|webp)$/Sxu';
167 private const SPACE_NOT_NL =
'(?:\t| |&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
175 # Allowed values for $this->mOutputType
205 public const MARKER_SUFFIX =
"-QINU`\"'\x7f";
207 private const HEADLINE_MARKER_REGEX =
'/^' . self::MARKER_PREFIX .
'-h-(\d+)-' . self::MARKER_SUFFIX .
'/';
223 public const TOC_PLACEHOLDER =
'<meta property="mw:PageProp/toc" />';
227 private array $mTagHooks = [];
229 private array $mFunctionHooks = [];
231 private array $mFunctionSynonyms = [ 0 => [], 1 => [] ];
233 private array $mStripList = [];
235 private array $mVarCache = [];
237 private array $mImageParams = [];
239 private array $mImageParamsMagicArray = [];
250 private string $mExtLinkBracketedRegex;
257 private int $mAutonumber = 0;
260 private int $mLinkID = 0;
261 private array $mIncludeSizes;
272 private array $mTplRedirCache;
276 private array $mDoubleUnderscores;
282 private bool $mShowToc;
283 private bool $mForceTocPosition;
284 private array $mTplDomCache;
288 # These are variables reset at least once per parse regardless of $clearState
296 # Deprecated "dynamic" properties
297 # These used to be dynamic properties added to the parser, but these
298 # have been deprecated since 1.42.
313 private Title $mTitle;
315 private int $mOutputType;
323 private bool $useParsoidFragments =
false;
330 private ?
int $mRevisionId =
null;
332 private ?
string $mRevisionTimestamp =
null;
334 private ?
string $mRevisionUser =
null;
336 private ?
int $mRevisionSize =
null;
338 private $mInputSize =
false;
353 private $mInParse =
false;
405 private LoggerInterface $logger,
423 $this->deprecateDynamicPropertiesAccess(
'1.42', __CLASS__ );
424 $this->deprecatePublicProperty(
'ot',
'1.35', __CLASS__ );
425 $this->deprecatePublicProperty(
'mTitle',
'1.35', __CLASS__ );
426 $this->deprecatePublicProperty(
'mOptions',
'1.35', __CLASS__ );
431 throw new BadMethodCallException(
'Direct construction of Parser not allowed' );
435 $this->mExtLinkBracketedRegex =
'/\[(((?i)' . $this->urlUtils->validProtocols() .
')' .
436 self::EXT_LINK_ADDR .
437 self::EXT_LINK_URL_CLASS .
'*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*)\]/Su';
439 $this->hookRunner =
new HookRunner( $hookContainer );
452 CoreParserFunctions::register(
454 new ServiceOptions( CoreParserFunctions::REGISTER_OPTIONS, $svcOptions )
456 $parserCoreTagHooks->
register( $this );
457 $this->initializeVariables();
459 $this->hookRunner->onParserFirstCallInit( $this );
468 if ( isset( $this->mLinkHolders ) ) {
470 unset( $this->mLinkHolders );
473 foreach ( $this as $name => $value ) {
474 unset( $this->$name );
482 $this->mInParse =
false;
484 $this->mPreprocessor = clone $this->mPreprocessor;
485 $this->mPreprocessor->resetParser( $this );
487 $this->hookRunner->onParserCloned( $this );
511 $this->mAutonumber = 0;
514 $this->getContentLanguageConverter(),
518 $this->mRevisionTimestamp =
null;
519 $this->mRevisionId =
null;
520 $this->mRevisionUser =
null;
521 $this->mRevisionSize =
null;
522 $this->mRevisionRecordObject =
null;
523 $this->mVarCache = [];
525 $this->currentRevisionCache =
null;
529 # Clear these on every parse, T6549
530 $this->mTplRedirCache = [];
531 $this->mTplDomCache = [];
533 $this->mShowToc =
true;
534 $this->mForceTocPosition =
false;
535 $this->mIncludeSizes = [
539 $this->mPPNodeCount = 0;
540 $this->mHighestExpansionDepth = 0;
541 $this->mHeadings = [];
542 $this->mDoubleUnderscores = [];
543 $this->mExpensiveFunctionCount = 0;
547 $this->hookRunner->onParserClearState( $this );
556 $this->mOptions->registerWatcher( $this->mOutput->recordOption( ... ) );
567 $ts = $this->mOptions->getTimestamp();
568 $date = DateTime::createFromFormat(
569 'YmdHis', $ts,
new DateTimeZone(
'UTC' )
571 if ( $this->hookContainer->isRegistered(
'ParserGetVariableValueTs' ) ) {
572 $s = $date->format(
'U' );
573 $this->hookRunner->onParserGetVariableValueTs( $this, $s );
599 $linestart =
true, $clearState =
true, $revid =
null
604 $text = strtr( $text,
"\x7f",
"?" );
605 $magicScopeVariable = $this->lock();
608 $text = str_replace(
"\000",
'', $text );
610 $this->startParse( $page, $options, self::OT_HTML, $clearState );
612 $this->currentRevisionCache =
null;
613 $this->mInputSize = strlen( $text );
614 $this->mOutput->resetParseStartTime();
616 $oldRevisionId = $this->mRevisionId;
617 $oldRevisionRecordObject = $this->mRevisionRecordObject;
618 $oldRevisionTimestamp = $this->mRevisionTimestamp;
619 $oldRevisionUser = $this->mRevisionUser;
620 $oldRevisionSize = $this->mRevisionSize;
621 if ( $revid !==
null ) {
622 $this->mRevisionId = $revid;
623 $this->mRevisionRecordObject =
null;
624 $this->mRevisionTimestamp =
null;
625 $this->mRevisionUser =
null;
626 $this->mRevisionSize =
null;
629 $text = $this->internalParse( $text );
630 $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
632 $text = $this->internalParseHalfParsed( $text,
true, $linestart );
642 && !isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
643 && !isset( $this->mDoubleUnderscores[
'notitleconvert'] )
644 && $this->mOutput->getDisplayTitle() ===
false
646 $converter = $this->getTargetLanguageConverter();
647 $titleText = $converter->getConvRuleTitle();
648 if ( $titleText !==
false ) {
652 [ $nsText, $nsSeparator, $mainText ] =
653 $this->splitDisplayTitle(
new HtmlArmor( $titleText ), $converter );
655 [ $nsText, $nsSeparator, $mainText ] = $converter->convertSplitTitle( $page );
656 $titleLang = $this->languageFactory->getLanguage( $converter->getPreferredVariant() );
657 $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText, $titleLang );
659 $this->mOutput->setDisplayTitleParts(
660 $nsText, $nsSeparator, $mainText,
new HtmlArmor( $titleText )
664 # Recording timing info. Must be called before finalizeAdaptiveCacheExpiry() and
665 # makeLimitReport(), which make use of the timing info.
666 $this->mOutput->recordTimeProfile();
668 # Compute runtime adaptive expiry if set
669 $this->mOutput->finalizeAdaptiveCacheExpiry();
671 # Warn if too many heavyweight parser functions were used
673 $this->limitationWarn(
'expensive-parserfunction',
674 $this->mExpensiveFunctionCount,
679 # Information on limits, for the benefit of users who try to skirt them
680 $this->makeLimitReport( $this->mOptions, $this->mOutput );
682 $this->mOutput->setFromParserOptions( $options );
684 $this->mOutput->setContentHolderText( $text );
686 $this->mRevisionId = $oldRevisionId;
687 $this->mRevisionRecordObject = $oldRevisionRecordObject;
688 $this->mRevisionTimestamp = $oldRevisionTimestamp;
689 $this->mRevisionUser = $oldRevisionUser;
690 $this->mRevisionSize = $oldRevisionSize;
691 $this->mInputSize =
false;
692 $this->currentRevisionCache =
null;
694 return $this->mOutput;
716 if ( $cpuTime !==
null ) {
718 sprintf(
"%.3f", $cpuTime )
724 sprintf(
"%.3f", $wallTime )
730 $revisionSize = $this->mInputSize !==
false ? $this->mInputSize :
731 $this->getRevisionSize();
736 [ $this->mIncludeSizes[
'post-expand'], $maxIncludeSize ]
739 [ $this->mIncludeSizes[
'arg'], $maxIncludeSize ]
748 foreach ( $this->mStripState->getLimitReport() as [ $key, $value ] ) {
752 $this->hookRunner->onParserLimitReportPrepare( $this, $parserOutput );
755 $dataByFunc = $this->mProfiler->getFunctionStats();
756 uasort( $dataByFunc,
static function ( $a, $b ) {
757 return $b[
'real'] <=> $a[
'real'];
760 foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
761 $profileReport[] = sprintf(
"%6.2f%% %8.3f %6d %s",
762 $item[
'%real'], $item[
'real'], $item[
'calls'],
763 htmlspecialchars( $item[
'name'] ) );
810 $text = $this->internalParse( $text, false, $frame );
834 $text = $this->recursiveTagParse( $text, $frame );
835 $text = $this->internalParseHalfParsed( $text,
false );
860 $text = $this->recursiveTagParse( $text, $frame );
861 $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
862 $text = $this->internalParseHalfParsed( $text,
true );
885 $magicScopeVariable = $this->lock();
886 $this->startParse( $page, $options, self::OT_PREPROCESS,
true );
887 if ( $revid !==
null ) {
888 $this->mRevisionId = $revid;
890 $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
891 $text = $this->replaceVariables( $text, $frame );
892 $text = $this->mStripState->unstripBoth( $text );
906 $text = $this->replaceVariables( $text, $frame );
907 $text = $this->mStripState->unstripBoth( $text );
927 $text = $msg->
params( $params )->plain();
929 # Parser (re)initialisation
930 $magicScopeVariable = $this->lock();
931 $this->startParse( $page, $options, self::OT_PLAIN,
true );
933 $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
934 $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
935 $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
936 $text = $this->mStripState->unstripBoth( $text );
948 $this->mUser = $user;
959 $this->setPage( $t );
979 $t = Title::makeTitle(
NS_SPECIAL,
'Badtitle/Parser' );
984 $t = Title::newFromPageReference( $t );
987 if ( $t->hasFragment() ) {
988 # Strip the fragment to avoid various odd effects
989 $this->mTitle = $t->createFragmentTarget(
'' );
1001 if ( $this->mTitle->isSpecial(
'Badtitle' ) ) {
1002 [ , $subPage ] = $this->specialPageFactory->resolveAlias( $this->mTitle->getDBkey() );
1004 if ( $subPage ===
'Missing' ) {
1005 wfDeprecated( __METHOD__ .
' without a Title set',
'1.34' );
1009 return $this->mTitle;
1018 return $this->mOutputType;
1027 $this->mOutputType = $ot;
1050 return $this->mOptions;
1059 $this->mOptions = $options;
1067 return $this->mLinkID++;
1075 $this->mLinkID = $id;
1087 $target = $this->mOptions->getTargetLanguage();
1089 if ( $target !==
null ) {
1091 } elseif ( $this->mOptions->getInterfaceMessage() ) {
1092 return $this->mOptions->getUserLangObj();
1095 return $this->getTitle()->getPageLanguage();
1106 return $this->mUser ?? $this->getOptions()->getUserIdentity();
1116 return $this->mPreprocessor;
1127 if ( !$this->mLinkRenderer ) {
1128 $this->mLinkRenderer = $this->linkRendererFactory->create();
1131 return $this->mLinkRenderer;
1141 return $this->magicWordFactory;
1151 return $this->contLang;
1161 return $this->badFileLookup;
1188 $taglist = implode(
'|', $elements );
1189 $start =
"/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1191 while ( $text !=
'' ) {
1192 $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1194 if ( count( $p ) < 5 ) {
1197 if ( count( $p ) > 5 ) {
1205 [ , $element, $attributes, $close, $inside ] = $p;
1208 $marker = self::MARKER_PREFIX .
"-$element-" . sprintf(
'%08X', $n++ ) . self::MARKER_SUFFIX;
1209 $stripped .= $marker;
1211 if ( $close ===
'/>' ) {
1212 # Empty element tag, <tag />
1217 if ( $element ===
'!--' ) {
1220 $end =
"/(<\\/$element\\s*>)/i";
1222 $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1224 if ( count( $q ) < 3 ) {
1225 # No end tag -- let it run out to the end of the text.
1229 [ , $tail, $text ] = $q;
1233 $matches[$marker] = [ $element,
1235 Sanitizer::decodeTagAttributes( $attributes ),
1236 "<$element$attributes$close$content$tail" ];
1247 return $this->mStripList;
1255 return $this->mStripState;
1268 $marker = self::MARKER_PREFIX .
"-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1269 $this->mMarkerIndex++;
1270 $this->mStripState->addGeneral( $marker, $text );
1280 private function handleTables(
string $text ): string {
1283 $td_history = []; # Is currently a td tag open?
1284 $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1285 $tr_history = []; # Is currently a tr tag open?
1286 $tr_attributes = []; # history of tr attributes
1287 $has_opened_tr = []; # Did
this table open a <tr> element?
1288 $indent_level = 0; # indent level of the table
1290 foreach ( $lines as $outLine ) {
1291 $line = trim( $outLine );
1293 if ( $line ===
'' ) { # empty line, go to next line
1294 $out .= $outLine .
"\n";
1298 $first_character = $line[0];
1299 $first_two = substr( $line, 0, 2 );
1302 if ( preg_match(
'/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1303 # First check if we are starting a new table
1304 $indent_level = strlen( $matches[1] );
1306 $attributes = $this->mStripState->unstripBoth( $matches[2] );
1307 $attributes = Sanitizer::fixTagAttributes( $attributes,
'table' );
1309 $outLine = str_repeat(
'<dl><dd>', $indent_level ) .
"<table{$attributes}>";
1310 $td_history[] =
false;
1311 $last_tag_history[] =
'';
1312 $tr_history[] =
false;
1313 $tr_attributes[] =
'';
1314 $has_opened_tr[] =
false;
1315 } elseif ( count( $td_history ) == 0 ) {
1316 # Don't do any of the following
1317 $out .= $outLine .
"\n";
1319 } elseif ( $first_two ===
'|}' ) {
1320 # We are ending a table
1321 $line =
'</table>' . substr( $line, 2 );
1322 $last_tag = array_pop( $last_tag_history );
1324 if ( !array_pop( $has_opened_tr ) ) {
1325 $line =
"<tr><td></td></tr>{$line}";
1328 if ( array_pop( $tr_history ) ) {
1329 $line =
"</tr>{$line}";
1332 if ( array_pop( $td_history ) ) {
1333 $line =
"</{$last_tag}>{$line}";
1335 array_pop( $tr_attributes );
1336 if ( $indent_level > 0 ) {
1337 $outLine = rtrim( $line ) . str_repeat(
'</dd></dl>', $indent_level );
1341 } elseif ( $first_two ===
'|-' ) {
1342 # Now we have a table row
1343 $line = preg_replace(
'#^\|-+#',
'', $line );
1345 # Whats after the tag is now only attributes
1346 $attributes = $this->mStripState->unstripBoth( $line );
1347 $attributes = Sanitizer::fixTagAttributes( $attributes,
'tr' );
1348 array_pop( $tr_attributes );
1349 $tr_attributes[] = $attributes;
1352 $last_tag = array_pop( $last_tag_history );
1353 array_pop( $has_opened_tr );
1354 $has_opened_tr[] =
true;
1356 if ( array_pop( $tr_history ) ) {
1360 if ( array_pop( $td_history ) ) {
1361 $line =
"</{$last_tag}>{$line}";
1365 $tr_history[] =
false;
1366 $td_history[] =
false;
1367 $last_tag_history[] =
'';
1368 } elseif ( $first_character ===
'|'
1369 || $first_character ===
'!'
1370 || $first_two ===
'|+'
1372 # This might be cell elements, td, th or captions
1373 if ( $first_two ===
'|+' ) {
1374 $first_character =
'+';
1375 $line = substr( $line, 2 );
1377 $line = substr( $line, 1 );
1381 if ( $first_character ===
'!' ) {
1382 $line = StringUtils::replaceMarkup(
'!!',
'||', $line );
1385 # Split up multiple cells on the same line.
1386 # FIXME : This can result in improper nesting of tags processed
1387 # by earlier parser steps.
1388 $cells = explode(
'||', $line );
1392 # Loop through each table cell
1393 foreach ( $cells as $cell ) {
1395 if ( $first_character !==
'+' ) {
1396 $tr_after = array_pop( $tr_attributes );
1397 if ( !array_pop( $tr_history ) ) {
1398 $previous =
"<tr{$tr_after}>\n";
1400 $tr_history[] =
true;
1401 $tr_attributes[] =
'';
1402 array_pop( $has_opened_tr );
1403 $has_opened_tr[] =
true;
1406 $last_tag = array_pop( $last_tag_history );
1408 if ( array_pop( $td_history ) ) {
1409 $previous =
"</{$last_tag}>\n{$previous}";
1412 if ( $first_character ===
'|' ) {
1414 } elseif ( $first_character ===
'!' ) {
1416 } elseif ( $first_character ===
'+' ) {
1417 $last_tag =
'caption';
1422 $last_tag_history[] = $last_tag;
1424 # A cell could contain both parameters and data
1425 $cell_data = explode(
'|', $cell, 2 );
1427 # T2553: Note that a '|' inside an invalid link should not
1428 # be mistaken as delimiting cell parameters
1429 # Bug T153140: Neither should language converter markup.
1430 if ( preg_match(
'/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1431 $cell =
"{$previous}<{$last_tag}>" . trim( $cell );
1432 } elseif ( count( $cell_data ) == 1 ) {
1434 $cell =
"{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1436 $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1437 $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1439 $cell =
"{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1443 $td_history[] =
true;
1446 $out .= $outLine .
"\n";
1449 # Closing open td, tr && table
1450 while ( count( $td_history ) > 0 ) {
1451 if ( array_pop( $td_history ) ) {
1454 if ( array_pop( $tr_history ) ) {
1457 if ( !array_pop( $has_opened_tr ) ) {
1458 $out .=
"<tr><td></td></tr>\n";
1461 $out .=
"</table>\n";
1464 # Remove trailing line-ending (b/c)
1465 if ( substr( $out, -1 ) ===
"\n" ) {
1466 $out = substr( $out, 0, -1 );
1469 # special case: don't return empty table
1470 if ( $out ===
"<table>\n<tr><td></td></tr>\n</table>" ) {
1490 public function internalParse( $text, $isMain =
true, $frame =
false ): string {
1493 # Hook to suspend the parser in this state
1494 if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1498 # if $frame is provided, then use $frame for replacing any variables
1500 # use frame depth to infer how include/noinclude tags should be handled
1501 # depth=0 means this is the top-level document; otherwise it's an included document
1502 if ( !$frame->depth ) {
1505 $flag = Preprocessor::DOM_FOR_INCLUSION;
1507 $dom = $this->preprocessToDom( $text, $flag );
1508 $text = $frame->
expand( $dom );
1510 # if $frame is not provided, then use old-style replaceVariables
1511 $text = $this->replaceVariables( $text );
1514 $text = Sanitizer::internalRemoveHtmlTags(
1518 function ( &$text, $frame =
false ) {
1519 $text = $this->mStripState->unstripBoth( $text );
1525 $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1527 # Tables need to come after variable replacement for things to work
1528 # properly; putting them before other transformations should keep
1529 # exciting things like link expansions from showing up in surprising
1531 $text = $this->handleTables( $text );
1533 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />', $text );
1535 $text = $this->handleDoubleUnderscore( $text );
1537 $text = $this->handleHeadings( $text );
1538 $text = $this->handleInternalLinks( $text );
1539 $text = $this->handleAllQuotes( $text );
1540 $text = $this->handleExternalLinks( $text );
1542 # handleInternalLinks may sometimes leave behind
1543 # absolute URLs, which have to be masked to hide them from handleExternalLinks
1544 $text = str_replace( self::MARKER_PREFIX .
'NOPARSE',
'', $text );
1546 $text = $this->handleMagicLinks( $text );
1547 $text = $this->finalizeHeadings( $text, $origText, $isMain );
1559 return $this->languageConverterFactory->getLanguageConverter(
1560 $this->getTargetLanguage()
1568 return $this->languageConverterFactory->getLanguageConverter(
1569 $this->getContentLanguage()
1581 return $this->hookContainer;
1609 return $this->hookRunner;
1621 private function internalParseHalfParsed(
string $text,
bool $isMain =
true,
bool $linestart =
true ): string {
1622 $text = $this->mStripState->unstripGeneral( $text );
1624 $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1626 $this->replaceLinkHoldersPrivate( $text );
1636 if ( !( $this->mOptions->getDisableContentConversion()
1637 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
1638 || $this->mOptions->getInterfaceMessage()
1639 || $this->mOptions->getUseParsoid()
1641 # The position of the convert() call should not be changed. it
1642 # assumes that the links are all replaced and the only thing left
1643 # is the <nowiki> mark.
1644 $converter = $this->getTargetLanguageConverter();
1645 $text = $converter->convert( $text );
1652 $this->mOutput->getTOCData(),
1653 $this->getTargetLanguage(),
1655 $converter?->getPreferredVariant()
1658 $this->mOutput->setLanguage(
new Bcp47CodeValue(
1659 LanguageCode::bcp47( $converter->getPreferredVariant() )
1662 $this->mOutput->setLanguage( $this->getTargetLanguage() );
1665 $text = $this->mStripState->unstripNoWiki( $text );
1667 $text = $this->mStripState->unstripGeneral( $text );
1669 $text = $this->tidy->tidy( $text, Sanitizer::armorFrenchSpaces( ... ) );
1672 $this->mOutput->setTitle( $this->getPage() );
1673 $this->hookRunner->onParserAfterTidy( $this, $text );
1689 private function handleMagicLinks(
string $text ): string {
1690 $prots = $this->urlUtils->validAbsoluteProtocols();
1691 $urlChar = self::EXT_LINK_URL_CLASS;
1692 $addr = self::EXT_LINK_ADDR;
1693 $space = self::SPACE_NOT_NL; # non-newline space
1694 $spdash =
"(?:-|$space)"; # a dash or a non-newline space
1695 $spaces =
"$space++"; # possessive match of 1 or more spaces
1696 $resultText = preg_replace_callback(
1698 (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1699 (<.*?>) | # m[2]: Skip stuff inside HTML elements' .
"
1700 (\b # m[3]: Free external links
1702 ($addr$urlChar*) # m[4]: Post-protocol path
1704 \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1706 \bISBN $spaces ( # m[6]: ISBN, capture number
1707 (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1708 (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1709 [0-9Xx] # check digit
1712 $this->magicLinkCallback( ... ),
1716 if ( $resultText ===
null ) {
1717 $this->logger->warning(
"Input text contains non-valid UTF-8 characters (" . __METHOD__ .
")" );
1727 private function magicLinkCallback( array $m ): string {
1728 if ( isset( $m[1] ) && $m[1] !==
'' ) {
1731 } elseif ( isset( $m[2] ) && $m[2] !==
'' ) {
1734 } elseif ( isset( $m[3] ) && $m[3] !==
'' ) {
1735 # Free external link
1736 return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1737 } elseif ( isset( $m[5] ) && $m[5] !==
'' ) {
1739 if ( str_starts_with( $m[0],
'RFC' ) ) {
1740 if ( !$this->mOptions->getMagicRFCLinks() ) {
1745 $cssClass =
'mw-magiclink-rfc';
1746 $trackingCat =
'magiclink-tracking-rfc';
1748 } elseif ( str_starts_with( $m[0],
'PMID' ) ) {
1749 if ( !$this->mOptions->getMagicPMIDLinks() ) {
1753 $urlmsg =
'pubmedurl';
1754 $cssClass =
'mw-magiclink-pmid';
1755 $trackingCat =
'magiclink-tracking-pmid';
1759 throw new UnexpectedValueException( __METHOD__ .
': unrecognised match type "' .
1760 substr( $m[0], 0, 20 ) .
'"' );
1762 $url =
wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1763 $this->addTrackingCategory( $trackingCat );
1771 } elseif ( isset( $m[6] ) && $m[6] !==
''
1772 && $this->mOptions->getMagicISBNLinks()
1776 $space = self::SPACE_NOT_NL; # non-newline space
1777 $isbn = preg_replace(
"/$space/",
' ', $isbn );
1778 $num = strtr( $isbn, [
1783 $this->addTrackingCategory(
'magiclink-tracking-isbn' );
1785 SpecialPage::getTitleFor(
'Booksources', $num ),
1788 'class' =>
'internal mw-magiclink-isbn',
1806 private function makeFreeExternalLink(
string $url,
int $numPostProto ): string {
1809 # The characters '<' and '>' (which were escaped by
1810 # internalRemoveHtmlTags()) should not be included in
1811 # URLs, per RFC 2396.
1812 # Make terminate a URL as well (bug T84937)
1815 '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1820 $trail = substr(
$url, $m2[0][1] ) . $trail;
1821 $url = substr(
$url, 0, $m2[0][1] );
1824 # Move trailing punctuation to $trail
1826 # If there is no left bracket, then consider right brackets fair game too
1827 if ( !str_contains(
$url,
'(' ) ) {
1831 $urlRev = strrev(
$url );
1832 $numSepChars = strspn( $urlRev, $sep );
1833 # Don't break a trailing HTML entity by moving the ; into $trail
1834 # This is in hot code, so use substr_compare to avoid having to
1835 # create a new string object for the comparison
1836 if ( $numSepChars && substr_compare(
$url,
";", -$numSepChars, 1 ) === 0 ) {
1837 # more optimization: instead of running preg_match with a $
1838 # anchor, which can be slow, do the match on the reversed
1839 # string starting at the desired offset.
1840 # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1841 if ( preg_match(
'/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1845 if ( $numSepChars ) {
1846 $trail = substr(
$url, -$numSepChars ) . $trail;
1847 $url = substr(
$url, 0, -$numSepChars );
1850 # Verify that we still have a real URL after trail removal, and
1851 # not just lone protocol
1852 if ( strlen( $trail ) >= $numPostProto ) {
1853 return $url . $trail;
1856 $url = Sanitizer::cleanUrl(
$url );
1858 # Is this an external image?
1859 $text = $this->maybeMakeExternalImage(
$url );
1860 if ( $text ===
false ) {
1861 # Not an image, make a link
1864 $this->getTargetLanguageConverter()->markNoConversion(
$url ),
1867 $this->getExternalLinkAttribs(
$url )
1869 # Register it in the output object...
1870 $this->mOutput->addExternalLink(
$url );
1872 return $text . $trail;
1881 private function handleHeadings(
string $text ): string {
1882 for ( $i = 6; $i >= 1; --$i ) {
1883 $h = str_repeat(
'=', $i );
1886 $text = preg_replace(
1887 "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m",
1888 "<h$i data-mw-wikitext>\\1</h$i>",
1894 $this->
getOutput()->setExtensionData(
'core:new-heading-attr',
true );
1905 private function handleAllQuotes(
string $text ): string {
1907 $lines = StringUtils::explode(
"\n", $text );
1908 foreach ( $lines as $line ) {
1909 $outtext .= $this->doQuotes( $line ) .
"\n";
1911 $outtext = substr( $outtext, 0, -1 );
1924 $arr = preg_split(
"/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1925 $countarr = count( $arr );
1926 if ( $countarr == 1 ) {
1935 for ( $i = 1; $i < $countarr; $i += 2 ) {
1936 $thislen = strlen( $arr[$i] );
1940 if ( $thislen == 4 ) {
1941 $arr[$i - 1] .=
"'";
1944 } elseif ( $thislen > 5 ) {
1948 $arr[$i - 1] .= str_repeat(
"'", $thislen - 5 );
1953 if ( $thislen == 2 ) {
1955 } elseif ( $thislen == 3 ) {
1957 } elseif ( $thislen == 5 ) {
1967 if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1968 $firstsingleletterword = -1;
1969 $firstmultiletterword = -1;
1971 for ( $i = 1; $i < $countarr; $i += 2 ) {
1972 if ( strlen( $arr[$i] ) == 3 ) {
1973 $x1 = substr( $arr[$i - 1], -1 );
1974 $x2 = substr( $arr[$i - 1], -2, 1 );
1975 if ( $x1 ===
' ' ) {
1976 if ( $firstspace == -1 ) {
1979 } elseif ( $x2 ===
' ' ) {
1980 $firstsingleletterword = $i;
1984 } elseif ( $firstmultiletterword == -1 ) {
1985 $firstmultiletterword = $i;
1991 if ( $firstsingleletterword > -1 ) {
1992 $arr[$firstsingleletterword] =
"''";
1993 $arr[$firstsingleletterword - 1] .=
"'";
1994 } elseif ( $firstmultiletterword > -1 ) {
1996 $arr[$firstmultiletterword] =
"''";
1997 $arr[$firstmultiletterword - 1] .=
"'";
1998 } elseif ( $firstspace > -1 ) {
2002 $arr[$firstspace] =
"''";
2003 $arr[$firstspace - 1] .=
"'";
2012 foreach ( $arr as $r ) {
2013 if ( ( $i % 2 ) == 0 ) {
2014 if ( $state ===
'both' ) {
2020 $thislen = strlen( $r );
2021 if ( $thislen == 2 ) {
2023 if ( $state ===
'i' ) {
2026 } elseif ( $state ===
'bi' ) {
2029 } elseif ( $state ===
'ib' ) {
2030 $output .=
'</b></i><b>';
2032 } elseif ( $state ===
'both' ) {
2033 $output .=
'<b><i>' . $buffer .
'</i>';
2039 } elseif ( $thislen == 3 ) {
2041 if ( $state ===
'b' ) {
2044 } elseif ( $state ===
'bi' ) {
2045 $output .=
'</i></b><i>';
2047 } elseif ( $state ===
'ib' ) {
2050 } elseif ( $state ===
'both' ) {
2051 $output .=
'<i><b>' . $buffer .
'</b>';
2057 } elseif ( $thislen == 5 ) {
2059 if ( $state ===
'b' ) {
2060 $output .=
'</b><i>';
2062 } elseif ( $state ===
'i' ) {
2063 $output .=
'</i><b>';
2065 } elseif ( $state ===
'bi' ) {
2066 $output .=
'</i></b>';
2068 } elseif ( $state ===
'ib' ) {
2069 $output .=
'</b></i>';
2071 } elseif ( $state ===
'both' ) {
2072 $output .=
'<i><b>' . $buffer .
'</b></i>';
2083 if ( $state ===
'b' || $state ===
'ib' ) {
2086 if ( $state ===
'i' || $state ===
'bi' || $state ===
'ib' ) {
2089 if ( $state ===
'bi' ) {
2093 if ( $state ===
'both' && $buffer ) {
2094 $output .=
'<b><i>' . $buffer .
'</i></b>';
2108 private function handleExternalLinks(
string $text ): string {
2109 $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2110 if ( $bits ===
false ) {
2114 $s = array_shift( $bits );
2117 while ( $i < count( $bits ) ) {
2120 $text = $bits[$i++];
2121 $trail = $bits[$i++];
2123 # The characters '<' and '>' (which were escaped by
2124 # internalRemoveHtmlTags()) should not be included in
2125 # URLs, per RFC 2396.
2127 if ( preg_match(
'/&(lt|gt);/',
$url, $m2, PREG_OFFSET_CAPTURE ) ) {
2128 $text = substr(
$url, $m2[0][1] ) .
' ' . $text;
2129 $url = substr(
$url, 0, $m2[0][1] );
2132 # If the link text is an image URL, replace it with an <img> tag
2133 # This happened by accident in the original parser, but some people used it extensively
2134 $img = $this->maybeMakeExternalImage( $text );
2135 if ( $img !==
false ) {
2141 # Set linktype for CSS
2144 # No link text, e.g. [http:
2145 if ( $text ==
'' ) {
2147 $langObj = $this->getTargetLanguage();
2148 $text =
'[' . $langObj->formatNum( ++$this->mAutonumber ) .
']';
2149 $linktype =
'autonumber';
2151 # Have link text, e.g. [http:
2153 [ $dtrail, $trail ] = Linker::splitTrail( $trail );
2157 if ( preg_match(
'/^(?:' . $this->urlUtils->validAbsoluteProtocols() .
')/', $text ) ) {
2158 $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2161 $url = Sanitizer::cleanUrl(
$url );
2163 # Use the encoded URL
2164 # This means that users can paste URLs directly into the text
2165 # Funny characters like ö aren't valid in URLs anyway
2166 # This was changed in August 2004
2169 new HtmlArmor( $text ),
2172 $this->getExternalLinkAttribs(
$url )
2173 ) . $dtrail . $trail;
2175 # Register link in the output object.
2176 $this->mOutput->addExternalLink(
$url );
2196 return MediaWikiServices::getInstance()->getLinkRenderer()
2197 ->getExternalLinkRel(
$url, $title );
2213 $rel = $this->getLinkRenderer()->getExternalLinkRel(
$url, $this->getTitle() ) ??
'';
2215 $target = $this->mOptions->getExternalLinkTarget();
2217 $attribs[
'target'] = $target;
2221 if ( $rel !==
'' ) {
2222 $attribs[
'rel'] = $rel;
2238 # Test for RFC 3986 IPv6 syntax
2239 $scheme =
'[a-z][a-z0-9+.-]*:';
2240 $userinfo =
'(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2241 $ipv6Host =
'\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2242 if ( preg_match(
"<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i",
$url, $m ) &&
2243 IPUtils::isValid( rawurldecode( $m[1] ) )
2245 $isIPv6 = rawurldecode( $m[1] );
2250 # Make sure unsafe characters are encoded
2251 $url = preg_replace_callback(
2252 '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]+/',
2253 static fn ( $m ) => rawurlencode( $m[0] ),
2258 $end = strlen(
$url );
2260 # Fragment part - 'fragment'
2261 $start = strpos(
$url,
'#' );
2262 if ( $start !==
false && $start < $end ) {
2263 $ret = self::normalizeUrlComponent(
2264 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}' ) . $ret;
2268 # Query part - 'query' minus &=+;
2269 $start = strpos(
$url,
'?' );
2270 if ( $start !==
false && $start < $end ) {
2271 $ret = self::normalizeUrlComponent(
2272 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}&=+;' ) . $ret;
2276 # Path part - 'pchar', remove dot segments
2277 # (find first '/' after the optional '
2278 $start = strpos(
$url,
'//' );
2279 $start = strpos(
$url,
'/', $start ===
false ? 0 : $start + 2 );
2280 if ( $start !==
false && $start < $end ) {
2281 $ret = UrlUtils::removeDotSegments( self::normalizeUrlComponent(
2282 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}/?' ) ) . $ret;
2286 # Scheme and host part - 'pchar'
2287 # (we assume no userinfo or encoded colons in the host)
2288 $ret = self::normalizeUrlComponent(
2289 substr(
$url, 0, $end ),
'"#%<>[\]^`{|}/?' ) . $ret;
2292 if ( $isIPv6 !==
false ) {
2293 $ipv6Host =
"%5B({$isIPv6})%5D";
2294 $ret = preg_replace(
2295 "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2304 private static function normalizeUrlComponent(
string $component,
string $unsafe ): string {
2305 $callback = static function ( $matches ) use ( $unsafe ) {
2306 $char = urldecode( $matches[0] );
2307 $ord = ord( $char );
2308 if ( $ord > 32 && $ord < 127 && !str_contains( $unsafe, $char ) ) {
2312 # Leave it escaped, but use uppercase for a-f
2313 return strtoupper( $matches[0] );
2316 return preg_replace_callback(
'/%[0-9A-Fa-f]{2}/', $callback, $component );
2327 private function maybeMakeExternalImage(
string $url ): string|false {
2328 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2329 $imagesexception = (bool)$imagesfrom;
2331 # $imagesfrom could be either a single string or an array of strings, parse out the latter
2332 if ( $imagesexception && is_array( $imagesfrom ) ) {
2333 $imagematch =
false;
2334 foreach ( $imagesfrom as $match ) {
2335 if ( str_starts_with(
$url, $match ) ) {
2340 } elseif ( $imagesexception ) {
2341 $imagematch = str_starts_with(
$url, $imagesfrom );
2343 $imagematch =
false;
2346 if ( $this->mOptions->getAllowExternalImages()
2347 || ( $imagesexception && $imagematch )
2349 if ( preg_match( self::EXT_IMAGE_REGEX,
$url ) ) {
2351 $text = Linker::makeExternalImage(
$url );
2354 if ( !$text && $this->mOptions->getEnableImageWhitelist()
2355 && preg_match( self::EXT_IMAGE_REGEX,
$url )
2357 $whitelist = explode(
2359 wfMessage(
'external_image_whitelist' )->inContentLanguage()->text()
2362 foreach ( $whitelist as $entry ) {
2363 # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2364 if ( $entry ===
'' || str_starts_with( $entry,
'#' ) ) {
2368 if ( preg_match(
'/' . str_replace(
'/',
'\\/', $entry ) .
'/i',
$url ) ) {
2369 # Image matches a whitelist entry
2370 $text = Linker::makeExternalImage(
$url );
2385 private function handleInternalLinks(
string $text ): string {
2386 $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2395 private function handleInternalLinks2( &$s ) {
2396 static $tc =
false, $e1, $e1_img;
2397 # the % is needed to support urlencoded titles as well
2399 $tc = Title::legalChars() .
'#%';
2400 # Match a link having the form [[namespace:link|alternate]]trail
2401 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2402 # Match cases where there is no "]]", which might still be images
2403 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
2406 $holders =
new LinkHolderArray(
2408 $this->getContentLanguageConverter(),
2409 $this->getHookContainer() );
2411 # split the entire text string on occurrences of [[
2412 $a = StringUtils::explode(
'[[',
' ' . $s );
2413 # get the first element (all text up to first [[), and remove the space we added
2416 $line = $a->current(); # Workaround
for broken ArrayIterator::next() that returns "
void"
2417 $s = substr( $s, 1 );
2419 $nottalk = !$this->getTitle()->isTalkPage();
2421 $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2423 if ( $useLinkPrefixExtension ) {
2424 # Match the end of a line for a word that's not followed by whitespace,
2425 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2426 $charset = $this->contLang->linkPrefixCharset();
2427 $e2 =
"/^((?>.*[^$charset]|))(.+)$/sDu";
2429 if ( preg_match( $e2, $s, $m ) ) {
2430 $first_prefix = $m[2];
2432 $first_prefix =
false;
2436 $first_prefix =
false;
2440 # Some namespaces don't allow subpages
2441 $useSubpages = $this->nsInfo->hasSubpages(
2445 # Loop for each link
2446 for ( ; $line !==
false && $line !==
null; $a->next(), $line = $a->current() ) {
2447 # Check for excessive memory usage
2448 if ( $holders->isBig() ) {
2450 # Do the existence check, replace the link holders and clear the array
2451 $holders->replace( $s );
2455 if ( $useLinkPrefixExtension ) {
2457 if ( preg_match( $e2, $s, $m ) ) {
2458 [ , $s, $prefix ] = $m;
2463 if ( $first_prefix ) {
2464 $prefix = $first_prefix;
2465 $first_prefix =
false;
2469 $might_be_img =
false;
2471 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2473 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2474 # [[Image:Foo.jpg|[http:
2475 # the real problem is with the $e1 regex
2477 # Still some problems for cases where the ] is meant to be outside punctuation,
2478 # and no image is in sight. See T4095.
2480 && substr( $m[3], 0, 1 ) ===
']'
2481 && strpos( $text,
'[' ) !==
false
2483 $text .=
']'; # so that handleExternalLinks($text) works later
2484 $m[3] = substr( $m[3], 1 );
2486 # fix up urlencoded title texts
2487 if ( str_contains( $m[1],
'%' ) ) {
2488 # Should anchors '#' also be rejected?
2489 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2492 } elseif ( preg_match( $e1_img, $line, $m ) ) {
2493 # Invalid, but might be an image with a link in its caption
2494 $might_be_img =
true;
2496 if ( str_contains( $m[1],
'%' ) ) {
2497 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2500 }
else { # Invalid form; output directly
2501 $s .= $prefix .
'[[' . $line;
2505 $origLink = ltrim( $m[1],
' ' );
2507 # Don't allow internal links to pages containing
2508 # PROTO: where PROTO is a valid URL protocol; these
2509 # should be external links.
2510 if ( preg_match(
'/^(?i:' . $this->urlUtils->validProtocols() .
')/', $origLink ) ) {
2511 $s .= $prefix .
'[[' . $line;
2515 # Make subpage if necessary
2516 if ( $useSubpages ) {
2517 $link = Linker::normalizeSubpageLink(
2518 $this->
getTitle(), $origLink, $text
2528 $unstrip = $this->mStripState->killMarkers( $link );
2529 $noMarkers = ( $unstrip === $link );
2531 $nt = $noMarkers ? Title::newFromText( $link ) : null;
2532 if ( $nt ===
null ) {
2533 $s .= $prefix .
'[[' . $line;
2537 $ns = $nt->getNamespace();
2538 $iw = $nt->getInterwiki();
2540 $noforce = !str_starts_with( $origLink,
':' );
2542 if ( $might_be_img ) { #
if this is actually an invalid link
2543 if ( $ns ===
NS_FILE && $noforce ) { # but might be an image
2546 # look at the next 'line' to see if we can close it there
2548 $next_line = $a->current();
2549 if ( $next_line ===
false || $next_line ===
null ) {
2552 $m = explode(
']]', $next_line, 3 );
2553 if ( count( $m ) == 3 ) {
2554 # the first ]] closes the inner link, the second the image
2556 $text .=
"[[{$m[0]}]]{$m[1]}";
2559 } elseif ( count( $m ) == 2 ) {
2560 # if there's exactly one ]] that's fine, we'll keep looking
2561 $text .=
"[[{$m[0]}]]{$m[1]}";
2563 # if $next_line is invalid too, we need look no further
2564 $text .=
'[[' . $next_line;
2569 # we couldn't find the end of this imageLink, so output it raw
2570 # but don't ignore what might be perfectly normal links in the text we've examined
2571 $holders->merge( $this->handleInternalLinks2( $text ) );
2572 $s .=
"{$prefix}[[$link|$text";
2573 # note: no $trail, because without an end, there *is* no trail
2576 }
else { # it
's not an image, so output it raw
2577 $s .= "{$prefix}[[$link|$text";
2578 # note: no $trail, because without an end, there *is* no trail
2583 $wasblank = ( $text == '' );
2587 # Strip off leading ':
'
2588 $text = substr( $text, 1 );
2591 # T6598 madness. Handle the quotes only if they come from the alternate part
2592 # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2593 # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2594 # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2595 $text = $this->doQuotes( $text );
2598 # Link not escaped by : , create the various objects
2599 if ( $noforce && !$nt->wasLocalInterwiki() ) {
2602 $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2603 $this->languageNameUtils->getLanguageName(
2605 LanguageNameUtils::AUTONYMS,
2606 LanguageNameUtils::DEFINED
2608 || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2611 # T26502: duplicates are resolved in ParserOutput
2612 $this->mOutput->addLanguageLink( $nt );
2618 $s = preg_replace( '/\n\s*$/
', '', $s . $prefix ) . $trail;
2622 if ( $ns === NS_FILE ) {
2624 # if no parameters were passed, $text
2625 # becomes something like "File:Foo.png",
2626 # which we don't want to pass on to the
2630 # recursively parse links inside the image caption
2631 # actually, this will parse them in any other parameters, too,
2632 # but it might be hard to fix that, and it doesn't matter ATM
2633 $text = $this->handleExternalLinks( $text );
2634 $holders->merge( $this->handleInternalLinks2( $text ) );
2636 # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2637 $s .= $prefix . $this->armorLinks(
2638 $this->makeImageInternal( $nt, $text, $holders ) ) . $trail;
2641 # Strip newlines from the left hand context of Category
2643 # See T2087, T87753, T174639, T359886
2644 $s = preg_replace(
'/\n\s*$/',
'', $s . $prefix ) . $trail;
2650 $this->mOutput->addCategory( $nt, $sortkey );
2656 # Self-link checking. For some languages, variants of the title are checked in
2657 # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2658 # for linking to a different variant.
2659 if ( $ns !==
NS_SPECIAL && $nt->equals( $this->getTitle() ) ) {
2660 $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text,
'', $trail,
'',
2661 Sanitizer::escapeIdForLink( $nt->getFragment() ) );
2665 # NS_MEDIA is a pseudo-namespace for linking directly to a file
2666 # @todo FIXME: Should do batch file existence checks, see comment below
2668 # Give extensions a chance to select the file revision for us
2671 $this->hookRunner->onBeforeParserFetchFileAndTitle(
2673 $this, $nt, $options, $descQuery
2675 # Fetch and register the file (file title may be different via hooks)
2676 [ $file, $nt ] = $this->fetchFileAndTitle( $nt, $options );
2677 # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2678 $s .= $prefix . $this->armorLinks(
2679 Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2683 # Some titles, such as valid special pages or files in foreign repos, should
2684 # be shown as bluelinks even though they're not included in the page table
2685 # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2686 # batch file existence checks for NS_FILE and NS_MEDIA
2687 if ( $iw ==
'' && $this->linkAlwaysKnownLookup->isAlwaysKnown( $nt ) ) {
2688 $this->mOutput->addLink( $nt );
2689 $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2691 # Links will be added to the output link list after checking
2692 $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2711 private function makeKnownLinkHolder(
2712 LinkTarget $nt,
string $text =
'',
2713 string $trail =
'',
string $prefix =
''
2715 [ $inside, $trail ] = Linker::splitTrail( $trail );
2717 if ( $text ==
'' ) {
2718 $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2722 $nt,
new HtmlArmor(
"$prefix$text$inside" )
2725 return $this->armorLinks( $link ) . $trail;
2738 private function armorLinks(
string $text ): string {
2739 return preg_replace(
'/\b((?i)' . $this->urlUtils->validProtocols() .
')/',
2740 self::MARKER_PREFIX .
"NOPARSE$1", $text );
2751 private function expandMagicVariable(
string $index, $frame =
false ): string {
2756 if ( isset( $this->mVarCache[$index] ) ) {
2757 return $this->mVarCache[$index];
2760 $value = CoreMagicVariables::expand(
2761 $this, $index,
new MWTimestamp( $this->getParseTime() ),
2762 $this->svcOptions, $this->logger, $frame
2765 if ( $value ===
null ) {
2769 $this->hookRunner->onParserGetVariableValueSwitch(
2771 $this, $fakeCache, $index, $value, $frame
2774 if ( $value ===
null ) {
2780 __METHOD__ .
" called hook for $index and got null",
2787 $this->mVarCache[$index] = $value;
2796 private function initializeVariables() {
2797 $variableIDs = $this->magicWordFactory->getVariableIDs();
2799 $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2800 $this->mSubstWords = $this->magicWordFactory->getSubstArray();
2822 return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2852 $text, $frame =
false, $argsOnly =
false, array $options = []
2854 # Is there any text? Also, Prevent too big inclusions!
2855 $textSize = strlen( $text );
2856 if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2860 if ( $frame ===
false ) {
2861 $frame = $this->getPreprocessor()->newFrame();
2862 } elseif ( !( $frame instanceof PPFrame ) ) {
2864 __METHOD__ .
" called using plain parameters instead of " .
2865 "a PPFrame instance. Creating custom frame.",
2868 $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2872 if ( $options[
'parsoidTopLevelCall'] ??
false ) {
2873 $ppFlags |= Preprocessor::START_IN_SOL_STATE;
2875 $dom = $this->preprocessToDom( $text, $ppFlags );
2876 $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2877 return $frame->
expand( $dom, $flags );
2882 $this->useParsoidFragments = $val;
2887 return $this->useParsoidFragments;
2918 # does no harm if $current and $max are present but are unnecessary for the message
2919 # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2920 # only during preview, and that would split the parser cache unnecessarily.
2921 $this->mOutput->addWarningMsg(
2922 "$limitationType-warning",
2923 Message::numParam( $current ),
2924 Message::numParam( $max )
2926 $this->addTrackingCategory(
"$limitationType-category" );
2957 $forceRawInterwiki =
false;
2959 $isChildObj =
false;
2961 $isLocalObj =
false;
2963 # Title object, where $text came from
2966 # $part1 is the bit before the first |, and must contain only title characters.
2967 # Various prefixes will be stripped from it later.
2968 $titleWithSpaces = $frame->
expand( $piece[
'title'] );
2969 $part1 = trim( $titleWithSpaces );
2972 # Original title text preserved for various purposes
2973 $originalTitle = $part1;
2975 # $args is a list of argument nodes, starting from index 0, not including $part1
2976 $args = $piece[
'parts'];
2978 $profileSection =
null;
2980 $sawDeprecatedTemplateEquals =
false;
2982 $isParsoid = $this->mOptions->getUseParsoid();
2987 $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
2988 $part1 = trim( $part1 );
2990 # Possibilities for substMatch: "subst", "safesubst" or FALSE
2991 # Decide whether to expand template or keep wikitext as-is.
2992 if ( $this->ot[
'wiki'] ) {
2993 if ( $substMatch ===
false ) {
2994 $literal =
true; # literal when in PST with no prefix
2996 $literal =
false; # expand when in PST with subst: or safesubst:
2999 if ( $substMatch ==
'subst' ) {
3000 $literal =
true; # literal when not in PST with plain subst:
3002 $literal =
false; # expand when not in PST with safesubst: or no prefix
3013 if ( !$found &&
$args->getLength() == 0 ) {
3014 $id = $this->mVariables->matchStartToEnd( $part1 );
3015 if ( $id !==
false ) {
3016 if ( str_contains( $part1,
':' ) ) {
3018 'Registering a magic variable with a name including a colon',
3019 '1.39',
false,
false
3022 $text = $this->expandMagicVariable( $id, $frame );
3027 # MSG, MSGNW and RAW
3030 $mwMsgnw = $this->magicWordFactory->get(
'msgnw' );
3031 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3034 # Remove obsolete MSG:
3035 $mwMsg = $this->magicWordFactory->get(
'msg' );
3036 $mwMsg->matchStartAndRemove( $part1 );
3040 $mwRaw = $this->magicWordFactory->get(
'raw' );
3041 if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3042 $forceRawInterwiki =
true;
3049 if ( preg_match(
'/[::]/u', $part1, $colonMatches, PREG_OFFSET_CAPTURE ) ) {
3050 [ $colonStr, $colonPos ] = $colonMatches[0];
3051 $func = substr( $part1, 0, $colonPos );
3052 $funcArgs = [ trim( substr( $part1, $colonPos + strlen( $colonStr ) ) ) ];
3053 $argsLength =
$args->getLength();
3054 for ( $i = 0; $i < $argsLength; $i++ ) {
3055 $funcArgs[] =
$args->item( $i );
3058 $result = $this->callParserFunction(
3059 $frame, $func, $funcArgs, $isParsoid && $piece[
'lineStart']
3063 if ( isset( $result[
'title'] ) ) {
3064 $title = $result[
'title'];
3066 if ( isset( $result[
'found'] ) ) {
3067 $found = $result[
'found'];
3069 if ( array_key_exists(
'text', $result ) ) {
3071 $text = $result[
'text'];
3073 if ( isset( $result[
'nowiki'] ) ) {
3074 $nowiki = $result[
'nowiki'];
3076 if ( isset( $result[
'isHTML'] ) ) {
3077 $isHTML = $result[
'isHTML'];
3079 if ( isset( $result[
'isRawHTML'] ) ) {
3080 $isRawHTML = $result[
'isRawHTML'];
3082 if ( isset( $result[
'forceRawInterwiki'] ) ) {
3083 $forceRawInterwiki = $result[
'forceRawInterwiki'];
3085 if ( isset( $result[
'isChildObj'] ) ) {
3086 $isChildObj = $result[
'isChildObj'];
3088 if ( isset( $result[
'isLocalObj'] ) ) {
3089 $isLocalObj = $result[
'isLocalObj'];
3094 # Finish mangling title and then check for loops.
3095 # Set $title to a Title object and $titleText to the PDBK
3098 # Split the title into page and subpage
3100 $relative = Linker::normalizeSubpageLink(
3101 $this->
getTitle(), $part1, $subpage
3103 if ( $part1 !== $relative ) {
3105 $ns = $this->
getTitle()->getNamespace();
3107 $title = Title::newFromText( $part1, $ns );
3109 $titleText = $title->getPrefixedText();
3110 # Check for language variants if the template is not found
3111 if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3112 $this->getTargetLanguageConverter()->findVariantLink( $part1, $title,
true );
3114 # Do recursion depth check
3115 $limit = $this->mOptions->getMaxTemplateDepth();
3116 if ( $frame->depth >= $limit ) {
3118 $text =
'<span class="error">'
3119 .
wfMessage(
'parser-template-recursion-depth-warning' )
3120 ->numParams( $limit )->inContentLanguage()->text()
3126 # Load from database
3127 if ( !$found && $title ) {
3128 $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3129 if ( !$title->isExternal() ) {
3130 if ( $title->isSpecialPage()
3131 && $this->mOptions->getAllowSpecialInclusion()
3132 && ( $this->ot[
'html'] || ( $this->useParsoidFragments && $this->ot[
'pre'] ) )
3134 $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3139 $argsLength =
$args->getLength();
3140 for ( $i = 0; $i < $argsLength; $i++ ) {
3141 $bits =
$args->item( $i )->splitArg();
3142 if ( strval( $bits[
'index'] ) ===
'' ) {
3143 $name = trim( $frame->
expand( $bits[
'name'], PPFrame::STRIP_COMMENTS ) );
3144 $value = trim( $frame->
expand( $bits[
'value'] ) );
3145 $pageArgs[$name] = $value;
3150 if ( $this->incrementExpensiveFunctionCount() ) {
3151 $context =
new RequestContext;
3152 $context->setTitle( $title );
3153 $context->setRequest(
new FauxRequest( $pageArgs ) );
3154 if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3155 $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3158 $context->setUser( User::newFromName(
'127.0.0.1',
false ) );
3160 $context->setLanguage( $this->mOptions->getUserLangObj() );
3161 $ret = $this->specialPageFactory->capturePath( $title, $context, $this->
getLinkRenderer() );
3163 $text = $context->getOutput()->getHTML();
3164 $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3167 if ( $specialPage && $specialPage->maxIncludeCacheTime() !==
false ) {
3168 $this->mOutput->updateRuntimeAdaptiveExpiry(
3169 $specialPage->maxIncludeCacheTime()
3174 } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3175 $found =
false; # access denied
3176 $this->logger->debug(
3178 ": template inclusion denied for " . $title->getPrefixedDBkey()
3181 [ $text, $title ] = $this->getTemplateDom( $title, $isParsoid && $piece[
'lineStart'] );
3182 if ( $text !==
false ) {
3187 $title->getDBkey() ===
'=' &&
3188 $originalTitle ===
'='
3194 $sawDeprecatedTemplateEquals =
true;
3199 # If the title is valid but undisplayable, make a link to it
3200 if ( !$found && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3201 $text =
"[[:$titleText]]";
3204 } elseif ( $title->isTrans() ) {
3205 # Interwiki transclusion
3206 if ( $this->ot[
'html'] && !$forceRawInterwiki ) {
3207 $text = $this->interwikiTransclude( $title,
'render' );
3210 $text = $this->interwikiTransclude( $title,
'raw' );
3211 # Preprocess it like a template
3212 $sol = ( $isParsoid && $piece[
'lineStart'] ) ? Preprocessor::START_IN_SOL_STATE : 0;
3213 $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION | $sol );
3219 # Do infinite loop check
3220 # This has to be done after redirect resolution to avoid infinite loops via redirects
3223 $text =
'<span class="error">'
3224 .
wfMessage(
'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3226 $this->addTrackingCategory(
'template-loop-category' );
3227 $this->mOutput->addWarningMsg(
3228 'template-loop-warning',
3231 $this->logger->debug( __METHOD__ .
": template loop broken at '$titleText'" );
3235 # If we haven't found text to substitute by now, we're done
3236 # Recover the source wikitext and return it
3239 if ( $profileSection ) {
3240 $this->mProfiler->scopedProfileOut( $profileSection );
3242 return [
'object' => $text ];
3245 # Expand DOM-style return values in a child frame
3246 if ( $isChildObj ) {
3247 # Clean up argument array
3248 $newFrame = $frame->
newChild( $args, $title );
3251 $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3252 } elseif ( $titleText !==
false && $newFrame->isEmpty() ) {
3253 # Expansion is eligible for the empty-frame cache
3254 $text = $newFrame->cachedExpand( $titleText, $text );
3256 # Uncached expansion
3257 $text = $newFrame->expand( $text );
3260 if ( $isLocalObj && $nowiki ) {
3261 $text = $frame->
expand( $text, PPFrame::RECOVER_ORIG );
3262 $isLocalObj =
false;
3265 if ( $profileSection ) {
3266 $this->mProfiler->scopedProfileOut( $profileSection );
3269 $sawDeprecatedTemplateEquals &&
3270 $this->mStripState->unstripBoth( $text ) !==
'='
3274 $this->addTrackingCategory(
'template-equals-category' );
3275 $this->mOutput->addWarningMsg(
'template-equals-warning' );
3278 # Replace raw HTML by a placeholder
3281 $text = $this->insertStripItem( $text );
3282 } elseif ( $isRawHTML ) {
3283 $marker = self::MARKER_PREFIX .
"-pf-"
3284 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3288 $this->mStripState->addNoWiki( $marker, $text );
3290 } elseif ( $nowiki && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3291 # Escape nowiki-style return values
3294 } elseif ( is_string( $text )
3295 && !$piece[
'lineStart']
3296 && preg_match(
'/^(?:{\\||:|;|#|\*)/', $text )
3304 $text =
"\n" . $text;
3307 if ( is_string( $text ) && !$this->incrementIncludeSize(
'post-expand', strlen( $text ) ) ) {
3308 # Error, oversize inclusion
3309 if ( $titleText !==
false ) {
3310 # Make a working, properly escaped link if possible (T25588)
3311 $text =
"[[:$titleText]]";
3313 # This will probably not be a working link, but at least it may
3314 # provide some hint of where the problem is
3315 $originalTitle = preg_replace(
'/^:/',
'', $originalTitle );
3316 $text =
"[[:$originalTitle]]";
3318 $text .= $this->insertStripItem(
'<!-- WARNING: template omitted, '
3319 .
'post-expand include size too large -->' );
3320 $this->limitationWarn(
'post-expand-template-inclusion' );
3323 if ( $isLocalObj ) {
3324 $ret = [
'object' => $text ];
3326 $ret = [
'text' => $text ];
3358 # Case sensitive functions
3359 if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3360 $function = $this->mFunctionSynonyms[1][$function];
3362 # Case insensitive functions
3363 $function = $this->contLang->lc( $function );
3364 if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3365 $function = $this->mFunctionSynonyms[0][$function];
3367 return [
'found' => false ];
3371 [ $callback, $flags ] = $this->mFunctionHooks[$function];
3373 $allArgs = [ $this ];
3374 if ( $flags & self::SFH_OBJECT_ARGS ) {
3375 # Convert arguments to PPNodes and collect for appending to $allArgs
3377 foreach ( $args as $k => $v ) {
3378 if ( $v instanceof
PPNode || $k === 0 ) {
3381 $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3385 # Add a frame parameter, and pass the arguments as an array
3386 $allArgs[] = $frame;
3387 $allArgs[] = $funcArgs;
3389 # Convert arguments to plain text and append to $allArgs
3390 foreach ( $args as $k => $v ) {
3391 if ( $v instanceof
PPNode ) {
3392 $allArgs[] = trim( $frame->
expand( $v ) );
3393 } elseif ( is_int( $k ) && $k >= 0 ) {
3394 $allArgs[] = trim( $v );
3396 $allArgs[] = trim(
"$k=$v" );
3401 $result = $callback( ...$allArgs );
3403 # The interface for function hooks allows them to return a wikitext
3404 # string or an array containing the string and any flags. This mungs
3405 # things around to match what this method should return.
3406 if ( !is_array( $result ) ) {
3412 if ( isset( $result[0] ) && !isset( $result[
'text'] ) ) {
3413 $result[
'text'] = $result[0];
3415 unset( $result[0] );
3421 $noparse = $result[
'noparse'] ??
true;
3423 $preprocessFlags = $result[
'preprocessFlags'] ?? 0;
3424 if ( $inSolState ) {
3425 $preprocessFlags |= Preprocessor::START_IN_SOL_STATE;
3427 $result[
'text'] = $this->preprocessToDom( $result[
'text'], $preprocessFlags );
3428 $result[
'isChildObj'] =
true;
3453 $cacheTitle = $title;
3454 $titleKey = CacheKeyHelper::getKeyForPage( $title );
3456 if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3457 [ $ns, $dbk ] = $this->mTplRedirCache[$titleKey];
3458 $title = Title::makeTitle( $ns, $dbk );
3459 $titleKey = CacheKeyHelper::getKeyForPage( $title );
3463 $titleKey =
"$titleKey:sol=" . ( $inSolState ?
"0" :
"1" );
3464 if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3465 return [ $this->mTplDomCache[$titleKey], $title ];
3468 # Cache miss, go to the database
3471 [ $text, $title ] = $this->fetchTemplateAndTitle( $title );
3474 if ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3475 return [
false, $title ];
3478 if ( $text ===
false ) {
3479 $this->mTplDomCache[$titleKey] =
false;
3480 return [
false, $title ];
3483 $flags = Preprocessor::DOM_FOR_INCLUSION | ( $inSolState ? Preprocessor::START_IN_SOL_STATE : 0 );
3484 $dom = $this->preprocessToDom( $text, $flags );
3485 $this->mTplDomCache[$titleKey] = $dom;
3487 if ( !$title->isSameLinkAs( $cacheTitle ) ) {
3488 $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3489 [ $title->getNamespace(), $title->getDBkey() ];
3492 return [ $dom, $title ];
3509 $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3510 if ( !$this->currentRevisionCache ) {
3511 $this->currentRevisionCache =
new MapCacheLRU( 100 );
3513 if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3514 $title = Title::newFromLinkTarget( $link );
3517 $this->mOptions->getCurrentRevisionRecordCallback()(
3521 if ( $revisionRecord ===
false ) {
3524 $revisionRecord =
null;
3526 $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3528 return $this->currentRevisionCache->get( $cacheKey );
3538 $key = CacheKeyHelper::getKeyForPage( $link );
3540 $this->currentRevisionCache &&
3541 $this->currentRevisionCache->has( $key )
3567 $page = Title::newFromLinkTarget( $link );
3582 return self::defaultFetchRevisionRecord(
3583 MediaWikiServices::getInstance()->getRevisionLookup(),
3597 $title = Title::newFromLinkTarget( $link );
3600 $templateCb = $this->mOptions->getTemplateCallback();
3601 $stuff = $templateCb( $title, $this );
3602 $revRecord = $stuff[
'revision-record'] ??
null;
3604 $text = $stuff[
'text'];
3605 if ( is_string( $stuff[
'text'] ) ) {
3607 $text = strtr( $text,
"\x7f",
"?" );
3609 $finalTitle = $stuff[
'finalTitle'] ?? $title;
3610 foreach ( ( $stuff[
'deps'] ?? [] ) as $dep ) {
3611 $this->mOutput->addTemplate( $dep[
'title'], $dep[
'page_id'], $dep[
'rev_id'] );
3612 if ( $dep[
'title']->equals( $this->getTitle() ) && $revRecord instanceof
RevisionRecord ) {
3615 $sha1 = $revRecord->getSha1();
3619 $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1,
'Self transclusion' );
3620 $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3624 return [ $text, $finalTitle ];
3659 $title = Title::castFromLinkTarget( $link );
3660 $text = $skip =
false;
3662 $finalTitle = $title;
3665 $contextTitle = $parser ? $parser->getTitle() :
null;
3667 # Loop to fetch the article, with up to 2 redirects
3669 # Note that $title (including redirect targets) could be
3670 # external; we do allow hooks a chance to redirect the
3671 # external title to a local one (which might be useful), but
3672 # are careful not to add external titles to the dependency
3675 for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3676 # Give extensions a chance to select the revision instead
3677 $revRecord =
null; # Assume no hook
3678 $origTitle = $title;
3679 $titleChanged =
false;
3682 # contain fragments or even represent an attempt to transclude
3683 # a broken or otherwise-missing
Title, which the hook may
3684 # fix up. Similarly, the $contextTitle may represent a special
3685 # page or other page which
"exists" as a parsing context but
3687 $contextTitle, $title,
3693 if ( !$title->isExternal() ) {
3696 'page_id' => $title->getArticleID(),
3703 if ( !$revRecord ) {
3705 $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3711 # Update title, as $revRecord may have been changed by hook
3712 $title = Title::newFromPageIdentity( $revRecord->getPage() );
3716 'page_id' => $revRecord->getPageId(),
3717 'rev_id' => $revRecord->getId(),
3719 } elseif ( !$title->isExternal() ) {
3722 'page_id' => $title->getArticleID(),
3726 if ( !$title->equals( $origTitle ) ) {
3727 # If we fetched a rev from a different title, register
3728 # the original title too...
3729 if ( !$origTitle->isExternal() ) {
3731 'title' => $origTitle,
3732 'page_id' => $origTitle->getArticleID(),
3736 $titleChanged =
true;
3738 # If there is no current revision, there is no page
3739 if ( $revRecord ===
null || $revRecord->getId() ===
null ) {
3743 if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3748 $finalTitle = $title;
3751 if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) {
3752 $content = $revRecord->getContent( SlotRecord::MAIN );
3753 $text = $content ? $content->getWikitextForTransclusion() :
null;
3758 if ( $text ===
false || $text ===
null ) {
3763 $content = $shadowPageLoader->
get( $title )?->getContentForTransclusion();
3765 $text = $content->getWikitextForTransclusion();
3775 $finalTitle = $title;
3776 $title = $content->getRedirectTarget();
3785 'revision-record' => $revRecord ?:
false,
3787 'finalTitle' => $finalTitle,
3801 $services = MediaWikiServices::getInstance();
3802 return self::defaultFetchTemplate(
3803 $services->getRevisionLookup(),
3804 new HookRunner( $services->getHookContainer() ),
3805 $services->getLinkCache(),
3806 $services->getShadowPageLoader(),
3821 $file = $this->fetchFileNoRegister( $link, $options );
3823 $time = $file ? $file->getTimestamp() :
false;
3824 $sha1 = $file ? $file->getSha1() :
false;
3825 # Register the file as a dependency...
3826 $this->mOutput->addImage( $link, $time, $sha1 );
3827 if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3828 # Update fetched file title after resolving redirects, etc.
3829 $link = $file->getTitle();
3830 $this->mOutput->addImage( $link, $time, $sha1 );
3833 $title = Title::newFromLinkTarget( $link );
3834 return [ $file, $title ];
3848 if ( isset( $options[
'broken'] ) ) {
3851 if ( isset( $options[
'sha1'] ) ) {
3852 $file = $this->repoGroup->findFileFromKey( $options[
'sha1'], $options );
3854 $link = TitleValue::newFromLinkTarget( $link );
3855 $file = $this->repoGroup->findFile( $link, $options );
3872 return wfMessage(
'scarytranscludedisabled' )->inContentLanguage()->text();
3876 $title = Title::newFromLinkTarget( $link );
3878 $url = $title->getFullURL( [
'action' => $action ] );
3879 if ( strlen(
$url ) > 1024 ) {
3880 return wfMessage(
'scarytranscludetoolong' )->inContentLanguage()->text();
3883 $wikiId = $title->getTransWikiID();
3885 $fname = __METHOD__;
3887 $cache = $this->wanCache;
3888 $data = $cache->getWithSetCallback(
3889 $cache->makeGlobalKey(
3890 'interwiki-transclude',
3891 ( $wikiId !==
false ) ? $wikiId :
'external',
3895 function ( $oldValue, &$ttl ) use (
$url, $fname, $cache ) {
3896 $req = $this->httpRequestFactory->create(
$url, [], $fname );
3898 $status = $req->execute();
3899 if ( !$status->isOK() ) {
3900 $ttl = $cache::TTL_UNCACHEABLE;
3901 } elseif ( $req->getResponseHeader(
'X-Database-Lagged' ) !==
null ) {
3903 $ttl = min( 30, $ttl );
3907 'text' => $status->isOK() ? $req->getContent() :
null,
3908 'code' => $req->getStatus()
3912 'checkKeys' => ( $wikiId !== false )
3913 ? [ $cache->makeGlobalKey(
'interwiki-page', $wikiId, $title->getDBkey() ) ]
3915 'pcGroup' =>
'interwiki-transclude:5',
3916 'pcTTL' => $cache::TTL_PROC_LONG
3920 if ( is_string( $data[
'text'] ) ) {
3921 $text = $data[
'text'];
3922 } elseif ( $data[
'code'] != 200 ) {
3924 $text =
wfMessage(
'scarytranscludefailed-httpstatus' )
3925 ->params(
$url, $data[
'code'] )->inContentLanguage()->text();
3927 $text =
wfMessage(
'scarytranscludefailed',
$url )->inContentLanguage()->text();
3944 $parts = $piece[
'parts'];
3945 $nameWithSpaces = $frame->
expand( $piece[
'title'] );
3946 $argName = trim( $nameWithSpaces );
3949 if ( $text ===
false && $parts->getLength() > 0
3950 && ( $this->ot[
'html']
3952 || ( $this->ot[
'wiki'] && $frame->
isTemplate() )
3955 # No match in frame, use the supplied default
3956 $object = $parts->item( 0 )->getChildren();
3958 if ( !$this->incrementIncludeSize(
'arg', strlen( $text ) ) ) {
3959 $error =
'<!-- WARNING: argument omitted, expansion size too large -->';
3960 $this->limitationWarn(
'post-expand-template-argument' );
3963 if ( $text ===
false && $object ===
false ) {
3967 if ( $error !==
false ) {
3970 if ( $object !==
false ) {
3971 $ret = [
'object' => $object ];
3973 $ret = [
'text' => $text ];
3981 return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
4000 static $errorStr =
'<span class="error">';
4002 $name = $frame->
expand( $params[
'name'] );
4003 if ( str_starts_with( $name, $errorStr ) ) {
4010 $attrText = !isset( $params[
'attr'] ) ?
'' : $frame->
expand( $params[
'attr'] );
4011 if ( str_starts_with( $attrText, $errorStr ) ) {
4019 $content = !isset( $params[
'inner'] ) ? null : $frame->
expand( $params[
'inner'] );
4021 $marker = self::MARKER_PREFIX .
"-$name-"
4022 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
4024 $normalizedName = strtolower( $name );
4025 $isNowiki = $normalizedName ===
'nowiki';
4026 $markerType = $isNowiki ?
'nowiki' :
'general';
4030 $extra = $isNowiki ? ( $content ??
'' ) : null;
4032 if ( $this->ot[
'html'] || ( $isNowiki && $this->useParsoidFragments ) ) {
4033 $attributes = Sanitizer::decodeTagAttributes( $attrText );
4035 if ( isset( $params[
'attributes'] ) ) {
4036 $attributes += $params[
'attributes'];
4039 if ( isset( $this->mTagHooks[$normalizedName] ) ) {
4042 $output = $this->mTagHooks[$normalizedName]( $content, $attributes, $this, $frame );
4044 $output =
'<span class="error">Invalid tag extension name: ' .
4045 htmlspecialchars( $normalizedName ) .
'</span>';
4048 if ( is_array( $output ) ) {
4051 $output = $flags[0];
4052 if ( isset( $flags[
'isRawHTML'] ) ) {
4053 $markerType =
'nowiki';
4055 if ( isset( $flags[
'markerType'] ) ) {
4056 $markerType = $flags[
'markerType'];
4062 if ( isset( $params[
'attributes'] ) ) {
4063 foreach ( $params[
'attributes'] as $attrName => $attrValue ) {
4064 $attrText .=
' ' . htmlspecialchars( $attrName ) .
'="' .
4065 htmlspecialchars( $this->getStripState()->unstripBoth( $attrValue ), ENT_COMPAT ) .
'"';
4068 if ( $content ===
null ) {
4069 $output =
"<$name$attrText/>";
4071 $close =
$params[
'close'] ===
null ?
'' : $frame->
expand( $params[
'close'] );
4072 if ( str_starts_with( $close, $errorStr ) ) {
4076 $output =
"<$name$attrText>$content$close";
4078 if ( $this->useParsoidFragments ) {
4079 $markerType =
'exttag';
4083 if ( $markerType ===
'none' ) {
4085 } elseif ( $markerType ===
'nowiki' ) {
4086 $this->mStripState->addNoWiki( $marker, $output, $extra );
4087 } elseif ( $markerType ===
'general' ) {
4088 $this->mStripState->addGeneral( $marker, $output );
4089 } elseif ( $markerType ===
'exttag' ) {
4090 $this->mStripState->addExtTag( $marker, $output, $frame );
4092 throw new UnexpectedValueException( __METHOD__ .
': invalid marker type' );
4104 private function incrementIncludeSize( $type, $size ) {
4105 if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4108 $this->mIncludeSizes[$type] += $size;
4118 $this->mExpensiveFunctionCount++;
4119 return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4129 private function handleDoubleUnderscore(
string $text ): string {
4130 # The position of __TOC__ needs to be recorded
4131 $mw = $this->magicWordFactory->get(
'toc' );
4133 if ( $mw->match( $text ) ) {
4134 $this->mShowToc =
true;
4135 $this->mForceTocPosition =
true;
4136 # record the alias used
4137 preg_match( $mw->getRegex(), $text, $tocAlias );
4139 # Set a placeholder. At the end we'll fill it in with the TOC.
4140 $text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
4142 # Only keep the first one.
4143 $text = $mw->replace(
'', $text );
4146 # Now match and remove the rest of them
4147 $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4148 $this->mDoubleUnderscores = $mwa->matchAndRemove( $text, returnAlias: true );
4150 # For consistency with all other double-underscores (see below)
4151 $this->mDoubleUnderscores[
'toc'] = $tocAlias[0];
4154 if ( isset( $this->mDoubleUnderscores[
'nogallery'] ) ) {
4155 $this->mOutput->setNoGallery(
true );
4157 if ( isset( $this->mDoubleUnderscores[
'notoc'] ) && !$this->mForceTocPosition ) {
4158 $this->mShowToc =
false;
4160 if ( isset( $this->mDoubleUnderscores[
'hiddencat'] )
4163 $this->addTrackingCategory(
'hidden-category-category' );
4165 # (T10068) Allow control over whether robots index a page.
4166 # __NOINDEX__ always overrides __INDEX__, see T16899
4168 isset( $this->mDoubleUnderscores[
'noindex'] ) &&
4169 $this->nsInfo->canUseNoindex( $this->getPage()->getNamespace() )
4171 $this->mOutput->setIndexPolicy(
'noindex' );
4172 $this->addTrackingCategory(
'noindex-category' );
4175 isset( $this->mDoubleUnderscores[
'index'] ) &&
4176 $this->nsInfo->canUseNoindex( $this->getPage()->getNamespace() )
4178 $this->mOutput->setIndexPolicy(
'index' );
4179 $this->addTrackingCategory(
'index-category' );
4182 foreach ( $this->mDoubleUnderscores as $key => $alias ) {
4183 # Cache all double underscores in the database
4184 $this->mOutput->setUnsortedPageProperty( $key );
4185 # Check for deprecated local aliases (T407289)
4186 $ascii = str_starts_with( $alias,
'__' ) && str_ends_with( $alias,
'__' );
4187 $wide = str_starts_with( $alias,
'__' ) && str_ends_with( $alias,
'__' );
4188 if ( !( $ascii || $wide ) ) {
4189 $this->addTrackingCategory(
'bad-double-underscore-category' );
4203 return $this->trackingCategories->addTrackingCategory(
4204 $this->mOutput, $msg, $this->getPage()
4221 ->inLanguage( $this->getTargetLanguage() )
4222 ->page( $this->getPage() );
4225 private function cleanUpTocLine( Node $container ) {
4226 '@phan-var Element|DocumentFragment $container';
4229 # * <sup> and <sub> (T10393)
4233 # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4234 # * <s> and <strike> (T35715)
4236 # We strip any parameter from accepted tags, except dir="rtl|ltr" from <span>,
4237 # to allow setting directionality in toc items.
4238 $allowedTags = [
'span',
'sup',
'sub',
'bdi',
'i',
'b',
's',
'strike',
'q' ];
4239 $node = $container->firstChild;
4240 while ( $node !==
null ) {
4241 $next = $node->nextSibling;
4242 if ( $node instanceof Element ) {
4243 $nodeName = DOMUtils::nodeName( $node );
4244 if ( in_array( $nodeName, [
'style',
'script' ],
true ) ) {
4245 # Remove any <style> or <script> tags (T198618)
4246 DOMCompat::remove( $node );
4247 } elseif ( in_array( $nodeName, $allowedTags,
true ) ) {
4250 foreach ( $node->attributes as $attr ) {
4252 $nodeName ===
'span' && $attr->name ===
'dir'
4253 && ( $attr->value ===
'rtl' || $attr->value ===
'ltr' )
4258 $removeAttrs[] = $attr;
4260 foreach ( $removeAttrs as $attr ) {
4261 $node->removeAttributeNode( $attr );
4263 $this->cleanUpTocLine( $node );
4264 # Strip '<span></span>', which is the result from the above if
4265 # <span id="foo"></span> is used to produce an additional anchor
4267 if ( $nodeName ===
'span' && !$node->hasChildNodes() ) {
4268 DOMCompat::remove( $node );
4272 if ( $node->firstChild !==
null ) {
4273 $next = $node->firstChild;
4275 while ( $childNode = $node->firstChild ) {
4276 $node->parentNode->insertBefore( $childNode, $node );
4279 DOMCompat::remove( $node );
4281 } elseif ( $node instanceof Comment ) {
4284 DOMCompat::remove( $node );
4305 private function finalizeHeadings(
string $text,
string $origText,
bool $isMain =
true ): string {
4306 # Inhibit editsection links
if requested in the page
4307 if ( isset( $this->mDoubleUnderscores[
'noeditsection'] ) ) {
4308 $maybeShowEditLink =
false;
4310 $maybeShowEditLink =
true;
4313 # Get all headlines for numbering them and adding funky stuff like [edit]
4314 # links - this is for later, but we need the number of headlines right now
4315 # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4316 # be trimmed here since whitespace in HTML headings is significant.
4318 $numMatches = preg_match_all(
4319 '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4324 # if there are fewer than 4 headlines in the article, do not show TOC
4325 # unless it's been explicitly enabled.
4326 $enoughToc = $this->mShowToc &&
4327 ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4329 # Allow user to stipulate that a page should have a "new section"
4330 # link added via __NEWSECTIONLINK__
4331 if ( isset( $this->mDoubleUnderscores[
'newsectionlink'] ) ) {
4332 $this->mOutput->setNewSection(
true );
4335 # Allow user to remove the "new section"
4336 # link via __NONEWSECTIONLINK__
4337 if ( isset( $this->mDoubleUnderscores[
'nonewsectionlink'] ) ) {
4338 $this->mOutput->setHideNewSection(
true );
4341 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4342 # override above conditions and always show TOC above first header
4343 if ( isset( $this->mDoubleUnderscores[
'forcetoc'] ) ) {
4344 $this->mShowToc =
true;
4348 if ( !$numMatches ) {
4354 $haveTocEntries =
false;
4356 # Ugh .. the TOC should have neat indentation levels which can be
4357 # passed to the skin functions. These are determined here
4360 $tocData =
new TOCData();
4361 $baseTitleText = $this->
getTitle()->getPrefixedDBkey();
4362 $oldType = $this->mOutputType;
4363 $this->setOutputType( self::OT_WIKI );
4364 $frame = $this->getPreprocessor()->newFrame();
4365 $root = $this->preprocessToDom( $origText );
4366 $node = $root->getFirstChild();
4370 $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4371 $domDocument = DOMCompat::newDocument();
4372 foreach ( $matches[3] as $headline ) {
4374 $isTemplate =
false;
4376 $sectionIndex =
false;
4377 if ( preg_match( self::HEADLINE_MARKER_REGEX, $headline, $markerMatches ) ) {
4378 $serial = (int)$markerMatches[1];
4379 [ $titleText, $sectionIndex ] = $this->mHeadings[$serial];
4380 $isTemplate = ( $titleText != $baseTitleText );
4381 $headline = ltrim( substr( $headline, strlen( $markerMatches[0] ) ) );
4384 $sectionMetadata = SectionMetadata::fromLegacy( [
4385 "fromtitle" => $titleText ?: null,
4386 "index" => $sectionIndex === false
4387 ?
'' : ( ( $isTemplate ?
'T-' :
'' ) . $sectionIndex )
4389 $tocData->addSection( $sectionMetadata );
4392 $level = (int)$matches[1][$headlineCount];
4393 $tocData->processHeading( $oldLevel, $level, $sectionMetadata );
4395 if ( $tocData->getCurrentTOCLevel() < $maxTocLevel ) {
4396 $haveTocEntries =
true;
4399 # Remove link placeholders by the link text.
4400 # <!--LINK number-->
4402 # link text with suffix
4403 # Do this before unstrip since link text can contain strip markers
4404 $fullyParsedHeadline = $this->replaceLinkHoldersText( $headline );
4406 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4407 $fullyParsedHeadline = $this->mStripState->unstripBoth( $fullyParsedHeadline );
4411 $fullyParsedHeadline = $this->tidy->tidy( $fullyParsedHeadline, Sanitizer::armorFrenchSpaces( ... ) );
4416 $wrappedHeadline =
"<h$level" . $matches[
'attrib'][$headlineCount] . $fullyParsedHeadline .
"</h$level>";
4420 $headlineDom = DOMUtils::parseHTMLToFragment( $domDocument, $wrappedHeadline );
4424 $h = $headlineDom->firstChild;
4425 $headingId = ( $h instanceof Element && DOMUtils::isHeading( $h ) ) ?
4426 DOMCompat::getAttribute( $h,
'id' ) :
null;
4428 $this->cleanUpTocLine( $headlineDom );
4432 $tocline = trim( DOMUtils::getFragmentInnerHTML( $headlineDom ) );
4435 $headlineText = trim( $headlineDom->textContent );
4437 if ( $headingId ===
null || $headingId ===
'' ) {
4438 $headingId = Sanitizer::normalizeSectionNameWhitespace( $headlineText );
4439 $headingId = self::normalizeSectionName( $headingId );
4442 # Create the anchor for linking from the TOC to the section
4443 $fallbackAnchor = Sanitizer::escapeIdForAttribute( $headingId, Sanitizer::ID_FALLBACK );
4444 $linkAnchor = Sanitizer::escapeIdForLink( $headingId );
4445 $anchor = Sanitizer::escapeIdForAttribute( $headingId, Sanitizer::ID_PRIMARY );
4446 if ( $fallbackAnchor === $anchor ) {
4447 # No reason to have both (in fact, we can't)
4448 $fallbackAnchor =
false;
4451 # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4452 $arrayKey = strtolower( $anchor );
4453 if ( $fallbackAnchor ===
false ) {
4454 $fallbackArrayKey =
false;
4456 $fallbackArrayKey = strtolower( $fallbackAnchor );
4459 if ( isset( $refers[$arrayKey] ) ) {
4460 for ( $i = 2; isset( $refers[
"{$arrayKey}_$i"] ); ++$i );
4462 $linkAnchor .=
"_$i";
4463 $refers[
"{$arrayKey}_$i"] =
true;
4465 $refers[$arrayKey] =
true;
4467 if ( $fallbackAnchor !==
false && isset( $refers[$fallbackArrayKey] ) ) {
4468 for ( $i = 2; isset( $refers[
"{$fallbackArrayKey}_$i"] ); ++$i );
4469 $fallbackAnchor .=
"_$i";
4470 $refers[
"{$fallbackArrayKey}_$i"] =
true;
4472 $refers[$fallbackArrayKey] =
true;
4475 # Add the section to the section tree
4476 # Find the DOM node for this header
4477 $noOffset = ( $isTemplate || $sectionIndex === false );
4478 while ( $node && !$noOffset ) {
4479 if ( $node->getName() ===
'h' ) {
4480 $bits = $node->splitHeading();
4481 if ( $bits[
'i'] == $sectionIndex ) {
4485 $cpOffset += mb_strlen(
4486 $this->mStripState->unstripBoth(
4487 $frame->
expand( $node, PPFrame::RECOVER_ORIG )
4490 $node = $node->getNextSibling();
4492 $sectionMetadata->line = $tocline;
4493 $sectionMetadata->codepointOffset = ( $noOffset ? null : $cpOffset );
4494 $sectionMetadata->anchor = $anchor;
4495 $sectionMetadata->linkAnchor = $linkAnchor;
4497 if ( $maybeShowEditLink && $sectionIndex !==
false ) {
4499 if ( $isTemplate ) {
4500 # Put a T flag in the section identifier, to indicate to extractSections()
4501 # that sections inside <includeonly> should be counted.
4502 $editsectionPage = $titleText;
4503 $editsectionSection =
"T-$sectionIndex";
4505 $editsectionPage = $this->
getTitle()->getPrefixedText();
4506 $editsectionSection = $sectionIndex;
4517 $editlink =
'<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4518 $editlink .=
'" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) .
'"';
4519 $editlink .=
'>' . htmlspecialchars( $headlineText ) .
'</mw:editsection>';
4530 $head[$headlineCount] =
"<h$level" . Html::expandAttributes( [
4531 'data-mw-anchor' => $anchor,
4532 'data-mw-fallback-anchor' => $fallbackAnchor,
4533 ] ) . $matches[
'attrib'][$headlineCount] . $headline . $editlink .
"</h$level>";
4538 $this->setOutputType( $oldType );
4540 # Never ever show TOC if no headers (or suppressed)
4541 $suppressToc = $this->mOptions->getSuppressTOC();
4542 if ( !$haveTocEntries ) {
4545 $addTOCPlaceholder =
false;
4547 if ( $isMain && !$suppressToc ) {
4555 $this->mOutput->setTOCData( $tocData );
4561 $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4562 if ( !$this->mForceTocPosition ) {
4563 $addTOCPlaceholder =
true;
4572 if ( !$this->mShowToc ) {
4573 $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
4577 # split up and insert constructed headlines
4578 $blocks = preg_split(
'/<h[1-6]\b[^>]*>.*?<\/h[1-6]>/is', $text );
4583 foreach ( $blocks as $block ) {
4585 if ( empty( $head[$i - 1] ) ) {
4586 $sections[$i] = $block;
4588 $sections[$i] = $head[$i - 1] . $block;
4594 if ( $addTOCPlaceholder ) {
4598 $sections[0] .= self::TOC_PLACEHOLDER .
"\n";
4601 return implode(
'', $sections );
4617 ?
string $preferredVariant =
null
4619 if ( $tocData ===
null ) {
4622 foreach ( $tocData->getSections() as $s ) {
4629 $s->line, $preferredVariant,
false
4634 $pieces = explode( $dot, $s->number );
4636 foreach ( $pieces as $i => $p ) {
4642 $s->number = $numbering;
4665 if ( $clearState ) {
4666 $magicScopeVariable = $this->lock();
4668 $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4669 $this->setUser( $user );
4672 $text = str_replace(
"\000",
'', $text );
4677 $text = TextContent::normalizeLineEndings( $text );
4680 $text = $this->pstPass2( $text, $user );
4682 $text = $this->mStripState->unstripBoth( $text );
4685 $text = rtrim( $text );
4687 $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4689 $this->setUser(
null ); # Reset
4702 private function pstPass2(
string $text, UserIdentity $user ): string {
4703 # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4704 # $this->contLang here in order to give everyone the same signature and use the default one
4705 # rather than the one selected in each user
's preferences. (see also T14815)
4706 $ts = $this->mOptions->getTimestamp();
4707 $timestamp = MWTimestamp::getLocalInstance( $ts );
4708 $ts = $timestamp->format( 'YmdHis
' );
4709 $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4711 $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4713 # Variable replacement
4714 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4715 $text = $this->replaceVariables( $text );
4717 # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4718 # which may corrupt this parser instance via its wfMessage()->text() call-
4721 if ( str_contains( $text, '~~~
' ) ) {
4722 $sigText = $this->getUserSig( $user );
4723 $text = strtr( $text, [
4725 '~~~~
' => "$sigText $d",
4728 # The main two signature forms used above are time-sensitive
4729 $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE, 'User signature detected
' );
4732 # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4733 $tc = '[
' . Title::legalChars() . ']
';
4734 $nc = '[ _0-9A-Za-z\x80-\xff-]
'; # Namespaces can use non-ascii!
4736 // [[ns:page (context)|]]
4737 $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4738 // [[ns:page(context)|]] (double-width brackets, added in r40257)
4739 $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4740 // [[ns:page (context), context|]] (using single, double-width or Arabic comma)
4741 $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4742 // [[|page]] (reverse pipe trick: add context from page title)
4743 $p2 = "/\[\[\\|($tc+)]]/";
4745 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4746 $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]
', $text );
4747 $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]
', $text );
4748 $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]
', $text );
4750 $t = $this->getTitle()->getText();
4752 if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4753 $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4754 } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4755 $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4757 # if there's no context, don
't bother duplicating the title
4758 $text = preg_replace( $p2, '[[\\1]]
', $text );
4779 public function getUserSig( UserIdentity $user, $nickname = false, $fancySig = null ): string {
4780 $username = $user->getName();
4782 # If not given, retrieve from the user object.
4783 if ( $nickname === false ) {
4784 $nickname = $this->userOptionsLookup->getOption( $user, 'nickname
' );
4787 $fancySig ??= $this->userOptionsLookup->getBoolOption( $user, 'fancysig
' );
4789 if ( $nickname === null || $nickname === '' ) {
4790 // Empty value results in the default signature (even when fancysig is enabled)
4791 $nickname = $username;
4792 } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4793 $nickname = $username;
4794 $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
4795 } elseif ( $fancySig !== false ) {
4796 # Sig. might contain markup; validate this
4797 $isValid = $this->validateSig( $nickname ) !== false;
4800 $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4801 if ( $isValid && $sigValidation === 'disallow
' ) {
4802 $parserOpts = new ParserOptions(
4803 $this->mOptions->getUserIdentity(),
4806 $validator = $this->signatureValidatorFactory
4807 ->newSignatureValidator( $user, null, $parserOpts );
4808 $isValid = !$validator->validateSignature( $nickname );
4812 # Validated; clean up (if needed) and return it
4813 return $this->cleanSig( $nickname, true );
4815 # Failed to validate; fall back to the default
4816 $nickname = $username;
4817 $this->logger->debug( __METHOD__ . ": $username has invalid signature." );
4821 # Make sure nickname doesn't get a sig in a sig
4822 $nickname = self::cleanSigInSig( $nickname );
4824 # If we're still here, make it a link to the user page
4825 if ( $this->userNameUtils->isTemp( $username ) ) {
4826 $msgName =
'signature-temp';
4827 } elseif ( $user->isRegistered() ) {
4828 $msgName =
'signature';
4830 $msgName =
'signature-anon';
4832 $message =
wfMessage( $msgName )->inContentLanguage()->page( $this->getPage() );
4838 if ( !str_contains( $message->plain(),
'~' ) && !str_starts_with( $message->plain(),
'$' ) ) {
4839 if ( $userText !==
'~' ) {
4840 $userText = preg_replace(
'/^~/',
'~', $userText );
4842 if ( $nickText !==
'~' ) {
4843 $nickText = preg_replace(
'/^~/',
'~', $nickText );
4847 return $message->params( $userText, $nickText )->text();
4863 $text = preg_replace(
4864 '/<(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)\s*>/i',
4868 return Xml::isWellFormedXmlFragment( $text ) ? $text :
false;
4882 public function cleanSig( $text, $parsing =
false ): string {
4884 $magicScopeVariable = $this->lock();
4887 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
4888 self::OT_PREPROCESS,
4893 # Option to disable this feature
4894 if ( !$this->mOptions->getCleanSignatures() ) {
4898 # @todo FIXME: Regex doesn't respect extension tags or nowiki
4899 # => Move this logic to braceSubstitution()
4900 $substWord = $this->magicWordFactory->get(
'subst' );
4901 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
4902 $substText =
'{{' . $substWord->getSynonym( 0 );
4904 $text = preg_replace( $substRegex, $substText, $text );
4905 $text = self::cleanSigInSig( $text );
4906 $dom = $this->preprocessToDom( $text );
4907 $frame = $this->getPreprocessor()->newFrame();
4908 $text = $frame->
expand( $dom );
4911 $text = $this->mStripState->unstripBoth( $text );
4925 $text = preg_replace(
'/~{3,5}/',
'', $text );
4949 if ( !str_contains( $text,
'mw:PageProp/toc' ) ) {
4954 return HtmlHelper::modifyElements(
4956 static function ( SerializerNode $node ): bool {
4957 $prop = $node->attrs[
'property'] ??
'';
4958 return $node->name ===
'meta' && $prop ===
'mw:PageProp/toc';
4960 static function ( SerializerNode $node ) use ( &$replaced, $toc ) {
4986 $outputType, $clearState =
true, $revId =
null
4988 $this->startParse( $page, $options, $outputType, $clearState );
4989 if ( $revId !==
null ) {
4990 $this->mRevisionId = $revId;
5001 $outputType, $clearState =
true
5003 $this->setPage( $page );
5004 $this->mOptions = $options;
5005 $this->setOutputType( $outputType );
5006 if ( $clearState ) {
5007 $this->clearState();
5021 static $executing = false;
5023 # Guard against infinite recursion
5029 $text = $this->preprocess( $text, $page ?? $this->mTitle, $options );
5054 public function setHook( $tag, callable $callback ) {
5055 $tag = strtolower( $tag );
5056 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
5057 throw new InvalidArgumentException(
"Invalid character {$m[0]} in setHook('$tag', ...) call" );
5059 $oldVal = $this->mTagHooks[$tag] ??
null;
5060 $this->mTagHooks[$tag] = $callback;
5061 if ( !in_array( $tag, $this->mStripList ) ) {
5062 $this->mStripList[] = $tag;
5073 $this->mTagHooks = [];
5074 $this->mStripList = [];
5126 $oldVal = $this->mFunctionHooks[$id][0] ?? null;
5127 $this->mFunctionHooks[$id] = [ $callback, $flags ];
5129 # Add to function cache
5130 $mw = $this->magicWordFactory->get( $id );
5132 $synonyms = $mw->getSynonyms();
5133 $sensitive = intval( $mw->isCaseSensitive() );
5135 foreach ( $synonyms as $syn ) {
5137 if ( !$sensitive ) {
5138 $syn = $this->contLang->lc( $syn );
5141 if ( !( $flags & self::SFH_NO_HASH ) ) {
5144 # Remove trailing colon (or Japanese double-width colon)
5145 if ( str_ends_with( $syn,
':' ) || str_ends_with( $syn,
':' ) ) {
5146 $syn = mb_substr( $syn, 0, -1 );
5148 $this->mFunctionSynonyms[$sensitive][$syn] = $id;
5160 return array_keys( $this->mFunctionHooks );
5169 private function replaceLinkHoldersPrivate(
string &$text ): void {
5170 $this->mLinkHolders->replace( $text );
5180 private function replaceLinkHoldersText(
string $text ): string {
5181 return $this->mLinkHolders->replaceText( $text );
5199 $mode = $params[
'mode'] ?? false;
5202 $ig = ImageGalleryBase::factory( $mode );
5205 $ig = ImageGalleryBase::factory();
5208 $ig->setContextTitle( $this->getTitle() );
5209 $ig->setShowBytes(
false );
5210 $ig->setShowDimensions(
false );
5211 $ig->setParser( $this );
5212 $ig->setHideBadImages();
5213 $ig->setAttributes( Sanitizer::validateTagAttributes( $params,
'ul' ) );
5215 $ig->setShowFilename( isset( $params[
'showfilename'] ) );
5216 if ( isset( $params[
'caption'] ) ) {
5220 $caption = $this->recursiveTagParse( $params[
'caption'] );
5221 $ig->setCaptionHtml( $caption );
5223 if ( isset( $params[
'perrow'] ) ) {
5224 $ig->setPerRow( $params[
'perrow'] );
5226 if ( isset( $params[
'widths'] ) ) {
5227 $ig->setWidths( $params[
'widths'] );
5229 if ( isset( $params[
'heights'] ) ) {
5230 $ig->setHeights( $params[
'heights'] );
5232 $ig->setAdditionalOptions( $params );
5234 $lines = StringUtils::explode(
"\n", $text );
5235 foreach ( $lines as $line ) {
5236 # match lines like these:
5237 # Image:someimage.jpg|This is some image
5239 preg_match(
"/^([^|]+)(\\|(.*))?$/", $line, $matches );
5241 if ( count( $matches ) == 0 ) {
5245 if ( str_contains( $matches[0],
'%' ) ) {
5246 $matches[1] = rawurldecode( $matches[1] );
5248 $title = Title::newFromText( $matches[1],
NS_FILE );
5249 if ( $title ===
null ) {
5250 # Bogus title. Ignore these so we don't bomb out later.
5254 # We need to get what handler the file uses, to figure out parameters.
5255 # Note, a hook can override the file name, and chose an entirely different
5256 # file (which potentially could be of a different type and have different handler).
5259 $this->hookRunner->onBeforeParserFetchFileAndTitle(
5261 $this, $title, $options, $descQuery
5263 # Don't register it now, as TraditionalImageGallery does that later.
5264 $file = $this->fetchFileNoRegister( $title, $options );
5265 $handler = $file ? $file->getHandler() :
false;
5268 'img_alt' =>
'gallery-internal-alt',
5269 'img_class' =>
'gallery-internal-class',
5270 'img_link' =>
'gallery-internal-link',
5273 $paramMap += $handler->getParamMap();
5276 unset( $paramMap[
'img_width'] );
5279 $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5283 $handlerOptions = [];
5287 if ( isset( $matches[3] ) ) {
5294 $matches[3] = $this->recursiveTagParse( $matches[3] );
5296 $parameterMatches = StringUtils::delimiterExplode(
5303 foreach ( $parameterMatches as $parameterMatch ) {
5304 [ $magicName, $match ] = $mwArray->matchVariableStartToEnd( trim( $parameterMatch ) );
5305 if ( !$magicName ) {
5307 $label = $parameterMatch;
5311 $paramName = $paramMap[$magicName];
5312 switch ( $paramName ) {
5313 case 'gallery-internal-alt':
5315 $alt = $this->stripAltText( $match );
5317 case 'gallery-internal-class':
5318 $imageOptions[
'class'] = $this->stripAltText( $match );
5320 case 'gallery-internal-link':
5321 $linkValue = $this->stripAltText( $match );
5322 if ( preg_match(
'/^-{R\|(.*)}-$/', $linkValue ) ) {
5325 $linkValue = substr( $linkValue, 4, -2 );
5327 [ $type, $target ] = $this->parseLinkParameter( $linkValue );
5329 if ( $type ===
'no-link' ) {
5332 $imageOptions[$type] = $target;
5337 if ( $handler->validateParam( $paramName, $match ) ) {
5338 $handlerOptions[$paramName] = $match;
5341 $this->logger->debug(
5342 "$parameterMatch failed parameter validation" );
5343 $label = $parameterMatch;
5350 if ( !$hasAlt && $label !==
'' ) {
5351 $alt = $this->stripAltText( $label );
5353 $imageOptions[
'title'] = $this->stripAltText( $label );
5356 $handlerOptions[
'targetlang'] = $this->getTargetLanguage()->getCode();
5359 $title, $label, $alt,
'', $handlerOptions,
5360 ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5363 $html = $ig->toHTML();
5364 $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5372 private function getImageParams( $handler ) {
5373 $handlerClass = $handler ? get_class( $handler ) :
'';
5374 if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5375 # Initialise static lists
5376 static $internalParamNames = [
5377 'horizAlign' => [
'left',
'right',
'center',
'none' ],
5378 'vertAlign' => [
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
5379 'bottom',
'text-bottom' ],
5380 'frame' => [
'thumbnail',
'framed',
'frameless',
'border',
5383 'manualthumb',
'upright',
'link',
'alt',
'class' ],
5385 static $internalParamMap;
5386 if ( !$internalParamMap ) {
5387 $internalParamMap = [];
5388 foreach ( $internalParamNames as $type => $names ) {
5389 foreach ( $names as $name ) {
5395 $magicName = str_replace(
'-',
'_',
"img_$name" );
5396 $internalParamMap[$magicName] = [ $type, $name ];
5401 # Add handler params
5402 # Since img_width is one of these, it is important it is listed
5403 # *after* the literal parameter names above (T372935).
5404 $paramMap = $internalParamMap;
5406 $handlerParamMap = $handler->getParamMap();
5407 foreach ( $handlerParamMap as $magic => $paramName ) {
5408 $paramMap[$magic] = [
'handler', $paramName ];
5412 $paramMap[
'img_width' ] = [
'handler',
'width' ];
5414 $this->mImageParams[$handlerClass] = $paramMap;
5415 $this->mImageParamsMagicArray[$handlerClass] =
5416 $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5418 return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5430 return $this->makeImageInternal(
5431 $link, $options, shouldReplaceLinkHolders: true
5449 public function makeImage( LinkTarget $link, $options, $holders =
false ): string {
5451 return $this->makeImageInternal(
5452 $link, $options, $holders ?: null, shouldReplaceLinkHolders: false
5465 private function makeImageInternal(
5469 bool $shouldReplaceLinkHolders =
false
5471 # Check
if the options text is of the form
"options|alt text"
5473 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5474 # * left no resizing, just left align. label is used for alt= only
5475 # * right same, but right aligned
5476 # * none same, but not aligned
5477 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5478 # * center center the image
5479 # * framed Keep original image size, no magnify-button.
5480 # * frameless like
'thumb' but without a frame. Keeps user preferences for width
5481 # * upright reduce width for upright images, rounded to full __0 px
5482 # * border draw a 1px border around the image
5483 # * alt Text for HTML alt attribute (defaults to empty)
5484 # * class Set a class for img node
5485 # * link Set the target of the image link. Can be external, interwiki, or local
5486 # vertical-align values (no % or length right now):
5496 #
Protect LanguageConverter markup when splitting into parts
5498 '-{',
'}-',
'|', $options, true
5501 # Give extensions a chance to select the file revision for us
5504 $title = Title::castFromLinkTarget( $link );
5505 $this->hookRunner->onBeforeParserFetchFileAndTitle(
5507 $this, $title, $options, $descQuery
5509 # Fetch and register the file (file title may be different via hooks)
5510 [ $file, $link ] = $this->fetchFileAndTitle( $link, $options );
5513 $handler = $file ? $file->getHandler() :
false;
5515 [ $paramMap, $mwArray ] = $this->getImageParams( $handler );
5518 $this->addTrackingCategory(
'broken-file-category' );
5521 # Process the input parameters
5523 $params = [
'frame' => [],
'handler' => [],
5524 'horizAlign' => [],
'vertAlign' => [] ];
5525 $seenformat =
false;
5526 foreach ( $parts as $part ) {
5527 [ $magicName, $value ] = $mwArray->matchVariableStartToEnd( trim( $part ) );
5529 if ( isset( $paramMap[$magicName] ) ) {
5530 [ $type, $paramName ] = $paramMap[$magicName];
5532 # Special case; width and height come in one variable together
5533 if ( $type ===
'handler' && $paramName ===
'width' ) {
5535 $parsedWidthParam = $this->parseWidthParam( $value,
true,
true );
5538 $validateFunc =
static function ( $name, $value ) use ( $handler ) {
5540 ? $handler->validateParam( $name, $value )
5543 if ( isset( $parsedWidthParam[
'width'] ) ) {
5544 $width = $parsedWidthParam[
'width'];
5545 if ( $validateFunc(
'width', $width ) ) {
5546 $params[$type][
'width'] = $width;
5550 if ( isset( $parsedWidthParam[
'height'] ) ) {
5551 $height = $parsedWidthParam[
'height'];
5552 if ( $validateFunc(
'height', $height ) ) {
5553 $params[$type][
'height'] = $height;
5557 # else no validation -- T15436
5559 if ( $type ===
'handler' ) {
5560 # Validate handler parameter
5561 $validated = $handler->validateParam( $paramName, $value );
5563 # Validate internal parameters
5564 switch ( $paramName ) {
5568 $value = $this->stripAltText( $value, $holders );
5571 [ $paramName, $value ] =
5572 $this->parseLinkParameter(
5573 $this->stripAltText( $value, $holders )
5577 if ( $paramName ===
'no-link' ) {
5583 # @todo FIXME: Possibly check validity here for
5584 # manualthumb? downstream behavior seems odd with
5585 # missing manual thumbs.
5586 $value = $this->stripAltText( $value, $holders );
5592 $validated = !$seenformat;
5596 # Most other things appear to be empty or numeric...
5597 $validated = ( $value ===
false || is_numeric( trim( $value ) ) );
5602 $params[$type][$paramName] = $value;
5606 if ( !$validated ) {
5611 # Process alignment parameters
5612 if ( $params[
'horizAlign'] !== [] ) {
5613 $params[
'frame'][
'align'] = array_key_first( $params[
'horizAlign'] );
5615 if ( $params[
'vertAlign'] !== [] ) {
5616 $params[
'frame'][
'valign'] = array_key_first( $params[
'vertAlign'] );
5619 $params[
'frame'][
'caption'] = $caption;
5621 # Will the image be presented in a frame, with the caption below?
5623 $hasVisibleCaption = isset( $params[
'frame'][
'framed'] )
5624 || isset( $params[
'frame'][
'thumbnail'] )
5625 || isset( $params[
'frame'][
'manualthumb'] );
5627 # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5628 # came to also set the caption, ordinary text after the image -- which
5629 # makes no sense, because that just repeats the text multiple times in
5630 # screen readers. It *also* came to set the title attribute.
5631 # Now that we have an alt attribute, we should not set the alt text to
5632 # equal the caption: that's worse than useless, it just repeats the
5633 # text. This is the framed/thumbnail case. If there's no caption, we
5634 # use the unnamed parameter for alt text as well, just for the time be-
5635 # ing, if the unnamed param is set and the alt param is not.
5636 # For the future, we need to figure out if we want to tweak this more,
5637 # e.g., introducing a title= parameter for the title; ignoring the un-
5638 # named parameter entirely for images without a caption; adding an ex-
5639 # plicit caption= parameter and preserving the old magic unnamed para-
5642 if ( !$hasVisibleCaption ) {
5644 if ( !isset( $params[
'frame'][
'alt'] ) && $caption !==
'' ) {
5645 # No alt text, use the "caption" for the alt text
5646 $params[
'frame'][
'alt'] = $this->stripAltText( $caption, $holders );
5648 # Use the "caption" for the tooltip text
5649 $params[
'frame'][
'title'] = $this->stripAltText( $caption, $holders );
5651 $params[
'handler'][
'targetlang'] = $this->getTargetLanguage()->getCode();
5654 $title = Title::castFromLinkTarget( $link );
5655 $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5657 # Linker does the rest
5658 $time = $options[
'time'] ??
false;
5659 $params[
'handler'][
'requestProvenance'] =
'parser';
5660 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
5662 $ret = Linker::makeImageLink( $this, $link, $file, $params[
'frame'], $params[
'handler'],
5663 $time, $descQuery, $defaultThumbSize );
5665 # Give the handler a chance to modify the parser object
5667 $handler->parserTransformHook( $this, $file );
5670 $this->modifyImageHtml( $file, $params, $ret );
5672 if ( $shouldReplaceLinkHolders ) {
5673 $this->replaceLinkHoldersPrivate( $ret );
5697 private function parseLinkParameter( $value ) {
5698 $chars = self::EXT_LINK_URL_CLASS;
5699 $addr = self::EXT_LINK_ADDR;
5700 $prots = $this->urlUtils->validProtocols();
5703 if ( $value ===
'' ) {
5705 } elseif ( preg_match(
"/^((?i)$prots)/", $value ) ) {
5706 if ( preg_match(
"/^((?i)$prots)$addr$chars*$/u", $value ) ) {
5707 $this->mOutput->addExternalLink( $value );
5737 if ( str_contains( $value,
'%' ) ) {
5738 $value = rawurldecode( $value );
5740 $linkTitle = Title::newFromText( $value );
5742 $this->mOutput->addLink( $linkTitle );
5743 $type =
'link-title';
5744 $target = $linkTitle;
5747 return [ $type, $target ];
5758 $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5761 private function stripAltText(
string $caption, ?
LinkHolderArray $holders =
null ): string {
5762 # Strip bad stuff out of the title (tooltip). We can
't just use
5763 # replaceLinkHoldersText() here, because if this function is called
5764 # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5765 if ( $holders !== null ) {
5766 $tooltip = $holders->replaceText( $caption );
5768 $tooltip = $this->replaceLinkHoldersText( $caption );
5771 # make sure there are no placeholders in thumbnail attributes
5772 # that are later expanded to html- so expand them now and
5774 $tooltip = $this->mStripState->unstripBoth( $tooltip );
5775 # Compatibility hack! In HTML certain entity references not terminated
5776 # by a semicolon are decoded (but not if we're in an attribute; that's
5777 # how link URLs get away without properly escaping & in queries).
5778 # But wikitext has always required semicolon-termination of entities,
5779 # so encode & where needed to avoid decode of semicolon-less entities.
5782 # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5783 $tooltip = preg_replace(
"/
5784 & # 1. entity prefix
5785 (?= # 2. followed by:
5786 (?: # a. one of the legacy semicolon-less named entities
5787 A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5788 C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5789 GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5790 O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5791 U(?:acute|circ|grave|uml)|Yacute|
5792 a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5793 c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5794 divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5795 frac(?:1(?:2|4)|34)|
5796 gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5797 i(?:acute|circ|excl|grave|quest|uml)|laquo|
5798 lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5799 m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5800 not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5801 o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5802 p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5803 s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5804 u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5806 (?:[^;]|$)) # b. and not followed by a semicolon
5807 # S = study, for efficiency
5808 /Sx",
'&', $tooltip );
5809 $tooltip = Sanitizer::stripAllTags( $tooltip );
5821 return array_keys( $this->mTagHooks );
5829 return $this->mFunctionSynonyms;
5837 return $this->urlUtils->validProtocols();
5871 private function extractSections(
5872 string $text,
string|
int $sectionId,
string $mode,
5875 $magicScopeVariable = $this->lock();
5878 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
5883 $frame = $this->getPreprocessor()->newFrame();
5885 # Process section extraction flags
5887 $sectionParts = explode(
'-', $sectionId );
5891 $sectionIndex = (int)array_pop( $sectionParts );
5892 foreach ( $sectionParts as $part ) {
5893 if ( $part ===
'T' ) {
5894 $flags |= Preprocessor::DOM_FOR_INCLUSION;
5898 # Check for empty input
5899 if ( $text ===
'' ) {
5900 # Only sections 0 and T-0 exist in an empty document
5901 if ( $sectionIndex === 0 ) {
5902 return $mode ===
'get' ?
'' : $newText;
5904 return $mode ===
'get' ? $newText : $text;
5908 # Preprocess the text
5909 $root = $this->preprocessToDom( $text, $flags );
5911 # <h> nodes indicate section breaks
5912 # They can only occur at the top level, so we can find them by iterating the root's children
5913 $node = $root->getFirstChild();
5915 # Find the target section
5916 if ( $sectionIndex === 0 ) {
5917 # Section zero doesn't nest, level=big
5918 $targetLevel = 1000;
5921 if ( $node->getName() ===
'h' ) {
5922 $bits = $node->splitHeading();
5923 if ( $bits[
'i'] == $sectionIndex ) {
5924 $targetLevel = $bits[
'level'];
5928 if ( $mode ===
'replace' ) {
5929 $outText .= $frame->
expand( $node, PPFrame::RECOVER_ORIG );
5931 $node = $node->getNextSibling();
5937 return $mode ===
'get' ? $newText : $text;
5940 # Find the end of the section, including nested sections
5942 if ( $node->getName() ===
'h' ) {
5943 $bits = $node->splitHeading();
5944 $curLevel = $bits[
'level'];
5946 if ( $bits[
'i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5950 if ( $mode ===
'get' ) {
5951 $outText .= $frame->
expand( $node, PPFrame::RECOVER_ORIG );
5953 $node = $node->getNextSibling();
5956 # Write out the remainder (in replace mode only)
5957 if ( $mode ===
'replace' ) {
5958 # Output the replacement text.
5959 # Add two newlines. Trailing whitespace in $newText is conventionally
5960 # stripped by the editor, so we need both newlines to restore the paragraph gap.
5961 # Only add trailing whitespace if there is newText.
5962 if ( $newText !=
"" ) {
5963 $outText .= $newText .
"\n\n";
5967 $outText .= $frame->
expand( $node, PPFrame::RECOVER_ORIG );
5968 $node = $node->getNextSibling();
5972 # Re-insert stripped tags
5973 $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5994 public function getSection( $text, $sectionId, $defaultText =
'' ): string|false {
5995 return $this->extractSections( $text, $sectionId,
'get', $defaultText );
6012 return $this->extractSections( $oldText, $sectionId,
'replace', $newText );
6045 $magicScopeVariable = $this->lock();
6048 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
6052 $frame = $this->getPreprocessor()->newFrame();
6053 $root = $this->preprocessToDom( $text, 0 );
6054 $node = $root->getFirstChild();
6066 $nodeText = $frame->
expand( $node, PPFrame::RECOVER_ORIG );
6067 if ( $node->getName() ===
'h' ) {
6068 $bits = $node->splitHeading();
6069 $sections[] = $currentSection;
6071 'index' => $bits[
'i'],
6072 'level' => $bits[
'level'],
6073 'offset' => $offset,
6074 'heading' => $nodeText,
6078 $currentSection[
'text'] .= $nodeText;
6080 $offset += strlen( $nodeText );
6081 $node = $node->getNextSibling();
6083 $sections[] = $currentSection;
6099 return $this->mRevisionId;
6109 if ( $this->mRevisionRecordObject ) {
6110 return $this->mRevisionRecordObject;
6112 if ( $this->mOptions->isMessage() ) {
6123 $rev = $this->mOptions->getCurrentRevisionRecordCallback()(
6136 if ( $this->mRevisionId ===
null && $rev->getId() ) {
6148 if ( $this->mRevisionId && $rev->getId() != $this->mRevisionId ) {
6149 $rev = MediaWikiServices::getInstance()
6150 ->getRevisionLookup()
6151 ->getRevisionById( $this->mRevisionId );
6154 $this->mRevisionRecordObject = $rev;
6156 return $this->mRevisionRecordObject;
6166 if ( $this->mRevisionTimestamp !== null ) {
6167 return $this->mRevisionTimestamp;
6170 # Use specified revision timestamp, falling back to the current timestamp
6171 $revObject = $this->getRevisionRecordObject();
6172 $timestamp = $revObject && $revObject->getTimestamp()
6173 ? $revObject->getTimestamp()
6174 : $this->mOptions->getTimestamp();
6175 $this->mOutput->setRevisionTimestampUsed( $timestamp );
6177 # The cryptic '' timezone parameter tells to use the site-default
6178 # timezone offset instead of the user settings.
6179 # Since this value will be saved into the parser cache, served
6180 # to other users, and potentially even used inside links and such,
6181 # it needs to be consistent for all visitors.
6182 $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp,
'' );
6184 return $this->mRevisionTimestamp;
6194 if ( $this->mRevisionUser === null ) {
6195 $revObject = $this->getRevisionRecordObject();
6197 # if this template is subst: the revision id will be blank,
6198 # so just use the current user's name
6199 if ( $revObject && $revObject->getUser() ) {
6200 $this->mRevisionUser = $revObject->getUser()->getName();
6201 } elseif ( $this->ot[
'wiki'] || $this->mOptions->getIsPreview() ) {
6202 $this->mRevisionUser = $this->getUserIdentity()->getName();
6204 # Note that we fall through here with
6205 # $this->mRevisionUser still null
6208 return $this->mRevisionUser;
6218 if ( $this->mRevisionSize ===
null ) {
6219 $revObject = $this->getRevisionRecordObject();
6221 # if this variable is subst: the revision id will be blank,
6222 # so just use the parser input size, because the own substitution
6223 # will change the size.
6224 $this->mRevisionSize = $revObject ? $revObject->getSize() : $this->mInputSize;
6226 return $this->mRevisionSize;
6229 private static function getSectionNameFromStrippedText(
string $text ): string {
6230 $text =
Sanitizer::normalizeSectionNameWhitespace( $text );
6231 $text = Sanitizer::decodeCharReferences( $text );
6232 $text = self::normalizeSectionName( $text );
6236 private static function makeAnchor(
string $sectionName ): string {
6237 return '#' . Sanitizer::escapeIdForLink( $sectionName );
6240 private function makeLegacyAnchor(
string $sectionName ): string {
6241 $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6242 if ( isset( $fragmentMode[1] ) && $fragmentMode[1] ===
'legacy' ) {
6244 $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
6246 $id = Sanitizer::escapeIdForLink( $sectionName );
6262 # Strip out wikitext links(they break the anchor)
6263 $text = $this->stripSectionName( $text );
6264 $sectionName = self::getSectionNameFromStrippedText( $text );
6265 return self::makeAnchor( $sectionName );
6281 # Strip out wikitext links(they break the anchor)
6282 $text = $this->stripSectionName( $text );
6283 $sectionName = self::getSectionNameFromStrippedText( $text );
6284 return $this->makeLegacyAnchor( $sectionName );
6294 $sectionName = self::getSectionNameFromStrippedText( $text );
6295 return self::makeAnchor( $sectionName );
6304 private static function normalizeSectionName(
string $text ): string {
6305 # T90902: ensure the same normalization is applied for IDs as to links
6308 $parts = $titleParser->splitTitleString(
"#$text" );
6309 }
catch ( MalformedTitleException ) {
6312 return $parts[
'fragment'];
6331 # Strip internal link markup
6332 $text = preg_replace(
'/\[\[:?([^[|]+)\|([^[]+)\]\]/',
'$2', $text );
6333 $text = preg_replace(
'/\[\[:?([^[]+)\|?\]\]/',
'$1', $text );
6335 # Strip external link markup
6336 # @todo FIXME: Not tolerant to blank link text
6338 # on how many empty links there are on the page - need to figure that out.
6339 $text = preg_replace(
6340 '/\[(?i:' . $this->urlUtils->validProtocols() .
')([^ ]+?) ([^[]+)\]/',
'$2', $text );
6342 # Parse wikitext quotes (italics & bold)
6343 $text = $this->doQuotes( $text );
6346 $text = StringUtils::delimiterReplace(
'<',
'>',
'', $text );
6371 while ( $i < strlen( $s ) ) {
6372 $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6373 if ( $markerStart ===
false ) {
6374 $out .= $callback( substr( $s, $i ) );
6377 $out .= $callback( substr( $s, $i, $markerStart - $i ) );
6378 $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6379 if ( $markerEnd ===
false ) {
6380 $out .= substr( $s, $markerStart );
6383 $markerEnd += strlen( self::MARKER_SUFFIX );
6384 $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6400 return $this->mStripState->killMarkers( $text );
6417 $parsedWidthParam = [];
6418 if ( $value ===
'' ) {
6419 return $parsedWidthParam;
6422 if ( !$localized ) {
6424 $mwArray = $this->magicWordFactory->newArray( [
'img_width' ] );
6425 [ $magicWord, $newValue ] = $mwArray->matchVariableStartToEnd( $value );
6426 $value = $magicWord ? $newValue : $value;
6429 # (T15500) In both cases (width/height and width only),
6430 # permit trailing "px" for backward compatibility.
6431 if ( $parseHeight && preg_match(
'/^([0-9]*)x([0-9]*)\s*(px)?\s*$/', $value, $m ) ) {
6432 $parsedWidthParam[
'width'] = intval( $m[1] );
6433 $parsedWidthParam[
'height'] = intval( $m[2] );
6434 if ( $m[3] ??
false ) {
6435 $this->addTrackingCategory(
'double-px-category' );
6437 } elseif ( preg_match(
'/^([0-9]*)\s*(px)?\s*$/', $value, $m ) ) {
6438 $parsedWidthParam[
'width'] = intval( $m[1] );
6439 if ( $m[2] ??
false ) {
6440 $this->addTrackingCategory(
'double-px-category' );
6443 return $parsedWidthParam;
6453 protected function lock(): ScopedCallback {
6454 if ( $this->mInParse ) {
6455 $message =
'Parser state cleared while parsing. Did you call Parser::parse recursively?';
6456 $xdebugMode = ini_get(
'xdebug.mode' );
6457 if ( $xdebugMode !==
false && str_contains( $xdebugMode,
'develop' ) ) {
6458 $message .= PHP_EOL .
'xdebug.mode=develop is known to cause this issue ' .
6459 '(xdebug bug #2222); consider ';
6460 $improvedMode = implode(
',',
6461 array_diff( explode(
',', $xdebugMode ),
6463 if ( $improvedMode !==
'' ) {
6464 $message .=
"using xdebug.mode=$improvedMode instead or ";
6466 $message .=
"disabling xdebug.";
6468 $message .= PHP_EOL .
'lock is held by: ' . $this->mInParse;
6469 throw new LogicException( $message );
6474 $e =
new RuntimeException;
6475 $this->mInParse = $e->getTraceAsString();
6477 $recursiveCheck =
new ScopedCallback(
function () {
6478 $this->mInParse =
false;
6481 return $recursiveCheck;
6492 return (
bool)$this->mInParse;
6507 if ( preg_match(
'/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && !str_contains( $m[1],
'</p>' ) ) {
6530 return self::splitPageTitle(
6533 $languageConverter ?? $this->getContentLanguageConverter()
6568 $pageTitle =
HtmlArmor::getHtml( $pageTitle );
6571 $parts = self::unformatPageTitle( $pageTitle );
6572 if ( $parts !==
null ) {
6573 [ $nsText, $nsSeparator, $mainText ] = $parts;
6578 [ $expectedNsText, $expectedNsSeparator ] =
6580 $prefixRegexp =
'/^(' . preg_quote( $expectedNsText,
'/' ) .
')(' .
6581 preg_quote( $expectedNsSeparator,
'/' ) .
')/i';
6583 if ( $expectedNsText !==
'' && preg_match( $prefixRegexp, $pageTitle, $matches ) ) {
6587 $nsText = $matches[1];
6588 $nsSeparator = $matches[2];
6589 $mainText = substr( $pageTitle, strlen( $matches[0] ) );
6596 $mainText = $pageTitle;
6603 new HtmlArmor( $nsText ),
6604 new HtmlArmor( $nsSeparator ),
6605 new HtmlArmor( $mainText ),
6623 if ( HtmlArmor::getHtml( $nsText ) !==
'' ) {
6624 $html .=
'<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) .
'</span>';
6625 $html .=
'<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) .
'</span>';
6627 $html .=
'<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) .
'</span>';
6628 if ( $titleLang !==
null ) {
6629 $html = Html::rawElement(
'span', [
6630 'lang' => $titleLang->getHtmlCode(),
6631 'dir' => $titleLang->getDir(),
6642 private const PAGE_TITLE_PLACEHOLDER =
'x-mw-page-title-part-x';
6654 private static function unformatPageTitle(
string $html ): ?array {
6655 static $nsPageTitleRegexp = null;
6656 static $mainNsPageTitleRegexp =
null;
6660 $nsPageTitleRegexp ??=
'/' . str_replace(
6661 preg_quote(
'(.+?)',
'/' ),
6663 preg_quote( self::formatPageTitle(
'(.+?)',
'(.+?)',
'(.+?)' ),
'/' )
6666 $mainNsPageTitleRegexp ??=
'/' . str_replace(
6667 preg_quote(
'(.+?)',
'/' ),
6669 preg_quote( self::formatPageTitle(
'',
'',
'(.+?)' ),
'/' )
6673 if ( preg_match( $nsPageTitleRegexp, $html, $matches ) ) {
6674 $parts = [ $matches[1], $matches[2], $matches[3] ];
6675 } elseif ( preg_match( $mainNsPageTitleRegexp, $html, $matches ) ) {
6676 $parts = [
'',
':', $matches[1] ];
6677 } elseif ( preg_match(
'/^<span lang="[^"<>]*" dir="[^"<>]*">(.*)<\/span>$/s', $html, $matches ) ) {
6681 return self::unformatPageTitle( $matches[1] );
6688 return array_map( Sanitizer::removeSomeTags( ... ), $parts );
6698 $posStart = strpos( $text,
'<body' );
6699 if ( $posStart ===
false ) {
6702 $posStart = strpos( $text,
'>', $posStart );
6703 if ( $posStart ===
false ) {
6708 $posEnd = strrpos( $text,
'</body>', $posStart );
6709 if ( $posEnd ===
false ) {
6711 return substr( $text, $posStart );
6713 return substr( $text, $posStart, $posEnd - $posStart );
6723 private function setOutputFlag( ParserOutputFlags|
string $flag,
string $reason ): void {
6724 $this->mOutput->setOutputFlag( $flag );
6725 if ( $flag instanceof ParserOutputFlags ) {
6727 $flag = $flag->value;
6729 $name = $this->
getTitle()->getPrefixedText();
6730 $this->logger->debug( __METHOD__ .
": set $flag flag on '$name'; $reason" );
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'EnableChunkedUploads'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'SharedUploadDBschema'=> null, 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> true, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -l $lang -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'MaxAnimatedWebPArea'=> 12500000, 'WebPThumbnailType'=>['webp', 'image/webp',], 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'RemoteVirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'SplitParsoidParserCache'=> false, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'PageShareSkinsEnabled'=>[], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', 'managesessions' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => [ 'class' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'needsPage' => false, ], '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', 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'RestTermsOfServiceUrl' => null, 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'HTTPUserAgentContact' => false, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], 'UseParsoidLinksUpdate' => null, 'UseParsoidMessages' => false, 'UseParsoidParser' => true, 'SiteLookup' => [ ], ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'SharedUploadDBschema' => [ 'string', 'null', ], 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'WebPThumbnailType' => 'array', 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'RemoteVirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'SplitParsoidParserCache' => 'boolean', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'PageShareSkinsEnabled' => 'array', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'HTTPUserAgentContact' => [ 'string', 'boolean', ], 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', 'UseParsoidLinksUpdate' => [ 'boolean', 'null', ], 'UseParsoidMessages' => [ 'boolean', 'null', ], 'UseParsoidParser' => 'boolean', 'SiteLookup' => 'object', ], 'mergeStrategy' => [ 'WebPThumbnailType' => 'replace', 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => [ 'integer', 'array', ], ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], 'skipRedirects' => [ 'type' => 'bool', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'availability' => [ 'type' => 'string', ], 'groups' => [ 'type' => 'array', ], ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]