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,
422 $this->deprecateDynamicPropertiesAccess(
'1.42', __CLASS__ );
423 $this->deprecatePublicProperty(
'ot',
'1.35', __CLASS__ );
424 $this->deprecatePublicProperty(
'mTitle',
'1.35', __CLASS__ );
425 $this->deprecatePublicProperty(
'mOptions',
'1.35', __CLASS__ );
430 throw new BadMethodCallException(
'Direct construction of Parser not allowed' );
434 $this->mExtLinkBracketedRegex =
'/\[(((?i)' . $this->urlUtils->validProtocols() .
')' .
435 self::EXT_LINK_ADDR .
436 self::EXT_LINK_URL_CLASS .
'*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*)\]/Su';
438 $this->hookRunner =
new HookRunner( $hookContainer );
451 CoreParserFunctions::register(
453 new ServiceOptions( CoreParserFunctions::REGISTER_OPTIONS, $svcOptions )
455 $parserCoreTagHooks->
register( $this );
456 $this->initializeVariables();
458 $this->hookRunner->onParserFirstCallInit( $this );
467 if ( isset( $this->mLinkHolders ) ) {
469 unset( $this->mLinkHolders );
472 foreach ( $this as $name => $value ) {
473 unset( $this->$name );
481 $this->mInParse =
false;
483 $this->mPreprocessor = clone $this->mPreprocessor;
484 $this->mPreprocessor->resetParser( $this );
486 $this->hookRunner->onParserCloned( $this );
510 $this->mAutonumber = 0;
513 $this->getContentLanguageConverter(),
517 $this->mRevisionTimestamp =
null;
518 $this->mRevisionId =
null;
519 $this->mRevisionUser =
null;
520 $this->mRevisionSize =
null;
521 $this->mRevisionRecordObject =
null;
522 $this->mVarCache = [];
524 $this->currentRevisionCache =
null;
528 # Clear these on every parse, T6549
529 $this->mTplRedirCache = [];
530 $this->mTplDomCache = [];
532 $this->mShowToc =
true;
533 $this->mForceTocPosition =
false;
534 $this->mIncludeSizes = [
538 $this->mPPNodeCount = 0;
539 $this->mHighestExpansionDepth = 0;
540 $this->mHeadings = [];
541 $this->mDoubleUnderscores = [];
542 $this->mExpensiveFunctionCount = 0;
546 $this->hookRunner->onParserClearState( $this );
555 $this->mOptions->registerWatcher( $this->mOutput->recordOption( ... ) );
566 $ts = $this->mOptions->getTimestamp();
567 $date = DateTime::createFromFormat(
568 'YmdHis', $ts,
new DateTimeZone(
'UTC' )
570 if ( $this->hookContainer->isRegistered(
'ParserGetVariableValueTs' ) ) {
571 $s = $date->format(
'U' );
572 $this->hookRunner->onParserGetVariableValueTs( $this, $s );
598 $linestart =
true, $clearState =
true, $revid =
null
603 $text = strtr( $text,
"\x7f",
"?" );
604 $magicScopeVariable = $this->lock();
607 $text = str_replace(
"\000",
'', $text );
609 $this->startParse( $page, $options, self::OT_HTML, $clearState );
611 $this->currentRevisionCache =
null;
612 $this->mInputSize = strlen( $text );
613 $this->mOutput->resetParseStartTime();
615 $oldRevisionId = $this->mRevisionId;
616 $oldRevisionRecordObject = $this->mRevisionRecordObject;
617 $oldRevisionTimestamp = $this->mRevisionTimestamp;
618 $oldRevisionUser = $this->mRevisionUser;
619 $oldRevisionSize = $this->mRevisionSize;
620 if ( $revid !==
null ) {
621 $this->mRevisionId = $revid;
622 $this->mRevisionRecordObject =
null;
623 $this->mRevisionTimestamp =
null;
624 $this->mRevisionUser =
null;
625 $this->mRevisionSize =
null;
628 $text = $this->internalParse( $text );
629 $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
631 $text = $this->internalParseHalfParsed( $text,
true, $linestart );
641 && !isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
642 && !isset( $this->mDoubleUnderscores[
'notitleconvert'] )
643 && $this->mOutput->getDisplayTitle() ===
false
645 $converter = $this->getTargetLanguageConverter();
646 $titleText = $converter->getConvRuleTitle();
647 if ( $titleText !==
false ) {
650 [ $nsText, $nsSeparator, $mainText ] = $converter->convertSplitTitle( $page );
653 $titleLang = $this->languageFactory->getLanguage( $converter->getPreferredVariant() );
654 $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText, $titleLang );
656 $this->mOutput->setTitleText( $titleText );
659 # Recording timing info. Must be called before finalizeAdaptiveCacheExpiry() and
660 # makeLimitReport(), which make use of the timing info.
661 $this->mOutput->recordTimeProfile();
663 # Compute runtime adaptive expiry if set
664 $this->mOutput->finalizeAdaptiveCacheExpiry();
666 # Warn if too many heavyweight parser functions were used
668 $this->limitationWarn(
'expensive-parserfunction',
669 $this->mExpensiveFunctionCount,
674 # Information on limits, for the benefit of users who try to skirt them
675 $this->makeLimitReport( $this->mOptions, $this->mOutput );
677 $this->mOutput->setFromParserOptions( $options );
679 $this->mOutput->setContentHolderText( $text );
681 $this->mRevisionId = $oldRevisionId;
682 $this->mRevisionRecordObject = $oldRevisionRecordObject;
683 $this->mRevisionTimestamp = $oldRevisionTimestamp;
684 $this->mRevisionUser = $oldRevisionUser;
685 $this->mRevisionSize = $oldRevisionSize;
686 $this->mInputSize =
false;
687 $this->currentRevisionCache =
null;
689 return $this->mOutput;
711 if ( $cpuTime !==
null ) {
713 sprintf(
"%.3f", $cpuTime )
719 sprintf(
"%.3f", $wallTime )
725 $revisionSize = $this->mInputSize !==
false ? $this->mInputSize :
726 $this->getRevisionSize();
731 [ $this->mIncludeSizes[
'post-expand'], $maxIncludeSize ]
734 [ $this->mIncludeSizes[
'arg'], $maxIncludeSize ]
743 foreach ( $this->mStripState->getLimitReport() as [ $key, $value ] ) {
747 $this->hookRunner->onParserLimitReportPrepare( $this, $parserOutput );
750 $dataByFunc = $this->mProfiler->getFunctionStats();
751 uasort( $dataByFunc,
static function ( $a, $b ) {
752 return $b[
'real'] <=> $a[
'real'];
755 foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
756 $profileReport[] = sprintf(
"%6.2f%% %8.3f %6d %s",
757 $item[
'%real'], $item[
'real'], $item[
'calls'],
758 htmlspecialchars( $item[
'name'] ) );
805 $text = $this->internalParse( $text, false, $frame );
829 $text = $this->recursiveTagParse( $text, $frame );
830 $text = $this->internalParseHalfParsed( $text,
false );
855 $text = $this->recursiveTagParse( $text, $frame );
856 $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
857 $text = $this->internalParseHalfParsed( $text,
true );
880 $magicScopeVariable = $this->lock();
881 $this->startParse( $page, $options, self::OT_PREPROCESS,
true );
882 if ( $revid !==
null ) {
883 $this->mRevisionId = $revid;
885 $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
886 $text = $this->replaceVariables( $text, $frame );
887 $text = $this->mStripState->unstripBoth( $text );
901 $text = $this->replaceVariables( $text, $frame );
902 $text = $this->mStripState->unstripBoth( $text );
922 $text = $msg->
params( $params )->plain();
924 # Parser (re)initialisation
925 $magicScopeVariable = $this->lock();
926 $this->startParse( $page, $options, self::OT_PLAIN,
true );
928 $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
929 $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
930 $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
931 $text = $this->mStripState->unstripBoth( $text );
943 $this->mUser = $user;
954 $this->setPage( $t );
974 $t = Title::makeTitle(
NS_SPECIAL,
'Badtitle/Parser' );
979 $t = Title::newFromPageReference( $t );
982 if ( $t->hasFragment() ) {
983 # Strip the fragment to avoid various odd effects
984 $this->mTitle = $t->createFragmentTarget(
'' );
996 if ( $this->mTitle->isSpecial(
'Badtitle' ) ) {
997 [ , $subPage ] = $this->specialPageFactory->resolveAlias( $this->mTitle->getDBkey() );
999 if ( $subPage ===
'Missing' ) {
1000 wfDeprecated( __METHOD__ .
' without a Title set',
'1.34' );
1004 return $this->mTitle;
1013 return $this->mOutputType;
1022 $this->mOutputType = $ot;
1045 return $this->mOptions;
1054 $this->mOptions = $options;
1062 return $this->mLinkID++;
1070 $this->mLinkID = $id;
1082 $target = $this->mOptions->getTargetLanguage();
1084 if ( $target !==
null ) {
1086 } elseif ( $this->mOptions->getInterfaceMessage() ) {
1087 return $this->mOptions->getUserLangObj();
1090 return $this->getTitle()->getPageLanguage();
1101 return $this->mUser ?? $this->getOptions()->getUserIdentity();
1111 return $this->mPreprocessor;
1122 if ( !$this->mLinkRenderer ) {
1123 $this->mLinkRenderer = $this->linkRendererFactory->create();
1126 return $this->mLinkRenderer;
1136 return $this->magicWordFactory;
1146 return $this->contLang;
1156 return $this->badFileLookup;
1183 $taglist = implode(
'|', $elements );
1184 $start =
"/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1186 while ( $text !=
'' ) {
1187 $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1189 if ( count( $p ) < 5 ) {
1192 if ( count( $p ) > 5 ) {
1200 [ , $element, $attributes, $close, $inside ] = $p;
1203 $marker = self::MARKER_PREFIX .
"-$element-" . sprintf(
'%08X', $n++ ) . self::MARKER_SUFFIX;
1204 $stripped .= $marker;
1206 if ( $close ===
'/>' ) {
1207 # Empty element tag, <tag />
1212 if ( $element ===
'!--' ) {
1215 $end =
"/(<\\/$element\\s*>)/i";
1217 $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1219 if ( count( $q ) < 3 ) {
1220 # No end tag -- let it run out to the end of the text.
1224 [ , $tail, $text ] = $q;
1230 Sanitizer::decodeTagAttributes( $attributes ),
1231 "<$element$attributes$close$content$tail" ];
1242 return $this->mStripList;
1250 return $this->mStripState;
1263 $marker = self::MARKER_PREFIX .
"-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1264 $this->mMarkerIndex++;
1265 $this->mStripState->addGeneral( $marker, $text );
1275 private function handleTables(
string $text ): string {
1278 $td_history = []; # Is currently a td tag open?
1279 $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1280 $tr_history = []; # Is currently a tr tag open?
1281 $tr_attributes = []; # history of tr attributes
1282 $has_opened_tr = []; # Did
this table open a <tr> element?
1283 $indent_level = 0; # indent level of the table
1285 foreach ( $lines as $outLine ) {
1286 $line = trim( $outLine );
1288 if ( $line ===
'' ) { # empty line, go to next line
1289 $out .= $outLine .
"\n";
1293 $first_character = $line[0];
1294 $first_two = substr( $line, 0, 2 );
1297 if ( preg_match(
'/^(:*)\s*\{\|(.*)$/', $line,
$matches ) ) {
1298 # First check if we are starting a new table
1299 $indent_level = strlen(
$matches[1] );
1301 $attributes = $this->mStripState->unstripBoth(
$matches[2] );
1302 $attributes = Sanitizer::fixTagAttributes( $attributes,
'table' );
1304 $outLine = str_repeat(
'<dl><dd>', $indent_level ) .
"<table{$attributes}>";
1305 $td_history[] =
false;
1306 $last_tag_history[] =
'';
1307 $tr_history[] =
false;
1308 $tr_attributes[] =
'';
1309 $has_opened_tr[] =
false;
1310 } elseif ( count( $td_history ) == 0 ) {
1311 # Don't do any of the following
1312 $out .= $outLine .
"\n";
1314 } elseif ( $first_two ===
'|}' ) {
1315 # We are ending a table
1316 $line =
'</table>' . substr( $line, 2 );
1317 $last_tag = array_pop( $last_tag_history );
1319 if ( !array_pop( $has_opened_tr ) ) {
1320 $line =
"<tr><td></td></tr>{$line}";
1323 if ( array_pop( $tr_history ) ) {
1324 $line =
"</tr>{$line}";
1327 if ( array_pop( $td_history ) ) {
1328 $line =
"</{$last_tag}>{$line}";
1330 array_pop( $tr_attributes );
1331 if ( $indent_level > 0 ) {
1332 $outLine = rtrim( $line ) . str_repeat(
'</dd></dl>', $indent_level );
1336 } elseif ( $first_two ===
'|-' ) {
1337 # Now we have a table row
1338 $line = preg_replace(
'#^\|-+#',
'', $line );
1340 # Whats after the tag is now only attributes
1341 $attributes = $this->mStripState->unstripBoth( $line );
1342 $attributes = Sanitizer::fixTagAttributes( $attributes,
'tr' );
1343 array_pop( $tr_attributes );
1344 $tr_attributes[] = $attributes;
1347 $last_tag = array_pop( $last_tag_history );
1348 array_pop( $has_opened_tr );
1349 $has_opened_tr[] =
true;
1351 if ( array_pop( $tr_history ) ) {
1355 if ( array_pop( $td_history ) ) {
1356 $line =
"</{$last_tag}>{$line}";
1360 $tr_history[] =
false;
1361 $td_history[] =
false;
1362 $last_tag_history[] =
'';
1363 } elseif ( $first_character ===
'|'
1364 || $first_character ===
'!'
1365 || $first_two ===
'|+'
1367 # This might be cell elements, td, th or captions
1368 if ( $first_two ===
'|+' ) {
1369 $first_character =
'+';
1370 $line = substr( $line, 2 );
1372 $line = substr( $line, 1 );
1376 if ( $first_character ===
'!' ) {
1377 $line = StringUtils::replaceMarkup(
'!!',
'||', $line );
1380 # Split up multiple cells on the same line.
1381 # FIXME : This can result in improper nesting of tags processed
1382 # by earlier parser steps.
1383 $cells = explode(
'||', $line );
1387 # Loop through each table cell
1388 foreach ( $cells as $cell ) {
1390 if ( $first_character !==
'+' ) {
1391 $tr_after = array_pop( $tr_attributes );
1392 if ( !array_pop( $tr_history ) ) {
1393 $previous =
"<tr{$tr_after}>\n";
1395 $tr_history[] =
true;
1396 $tr_attributes[] =
'';
1397 array_pop( $has_opened_tr );
1398 $has_opened_tr[] =
true;
1401 $last_tag = array_pop( $last_tag_history );
1403 if ( array_pop( $td_history ) ) {
1404 $previous =
"</{$last_tag}>\n{$previous}";
1407 if ( $first_character ===
'|' ) {
1409 } elseif ( $first_character ===
'!' ) {
1411 } elseif ( $first_character ===
'+' ) {
1412 $last_tag =
'caption';
1417 $last_tag_history[] = $last_tag;
1419 # A cell could contain both parameters and data
1420 $cell_data = explode(
'|', $cell, 2 );
1422 # T2553: Note that a '|' inside an invalid link should not
1423 # be mistaken as delimiting cell parameters
1424 # Bug T153140: Neither should language converter markup.
1425 if ( preg_match(
'/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1426 $cell =
"{$previous}<{$last_tag}>" . trim( $cell );
1427 } elseif ( count( $cell_data ) == 1 ) {
1429 $cell =
"{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1431 $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1432 $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1434 $cell =
"{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1438 $td_history[] =
true;
1441 $out .= $outLine .
"\n";
1444 # Closing open td, tr && table
1445 while ( count( $td_history ) > 0 ) {
1446 if ( array_pop( $td_history ) ) {
1449 if ( array_pop( $tr_history ) ) {
1452 if ( !array_pop( $has_opened_tr ) ) {
1453 $out .=
"<tr><td></td></tr>\n";
1456 $out .=
"</table>\n";
1459 # Remove trailing line-ending (b/c)
1460 if ( substr( $out, -1 ) ===
"\n" ) {
1461 $out = substr( $out, 0, -1 );
1464 # special case: don't return empty table
1465 if ( $out ===
"<table>\n<tr><td></td></tr>\n</table>" ) {
1485 public function internalParse( $text, $isMain =
true, $frame =
false ): string {
1488 # Hook to suspend the parser in this state
1489 if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1493 # if $frame is provided, then use $frame for replacing any variables
1495 # use frame depth to infer how include/noinclude tags should be handled
1496 # depth=0 means this is the top-level document; otherwise it's an included document
1497 if ( !$frame->depth ) {
1500 $flag = Preprocessor::DOM_FOR_INCLUSION;
1502 $dom = $this->preprocessToDom( $text, $flag );
1503 $text = $frame->
expand( $dom );
1505 # if $frame is not provided, then use old-style replaceVariables
1506 $text = $this->replaceVariables( $text );
1509 $text = Sanitizer::internalRemoveHtmlTags(
1513 function ( &$text, $frame =
false ) {
1514 $text = $this->mStripState->unstripBoth( $text );
1520 $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1522 # Tables need to come after variable replacement for things to work
1523 # properly; putting them before other transformations should keep
1524 # exciting things like link expansions from showing up in surprising
1526 $text = $this->handleTables( $text );
1528 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />', $text );
1530 $text = $this->handleDoubleUnderscore( $text );
1532 $text = $this->handleHeadings( $text );
1533 $text = $this->handleInternalLinks( $text );
1534 $text = $this->handleAllQuotes( $text );
1535 $text = $this->handleExternalLinks( $text );
1537 # handleInternalLinks may sometimes leave behind
1538 # absolute URLs, which have to be masked to hide them from handleExternalLinks
1539 $text = str_replace( self::MARKER_PREFIX .
'NOPARSE',
'', $text );
1541 $text = $this->handleMagicLinks( $text );
1542 $text = $this->finalizeHeadings( $text, $origText, $isMain );
1554 return $this->languageConverterFactory->getLanguageConverter(
1555 $this->getTargetLanguage()
1563 return $this->languageConverterFactory->getLanguageConverter(
1564 $this->getContentLanguage()
1576 return $this->hookContainer;
1604 return $this->hookRunner;
1616 private function internalParseHalfParsed(
string $text,
bool $isMain =
true,
bool $linestart =
true ): string {
1617 $text = $this->mStripState->unstripGeneral( $text );
1619 $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1621 $this->replaceLinkHoldersPrivate( $text );
1631 if ( !( $this->mOptions->getDisableContentConversion()
1632 || isset( $this->mDoubleUnderscores[
'nocontentconvert'] )
1633 || $this->mOptions->getInterfaceMessage()
1634 || $this->mOptions->getUseParsoid()
1636 # The position of the convert() call should not be changed. it
1637 # assumes that the links are all replaced and the only thing left
1638 # is the <nowiki> mark.
1639 $converter = $this->getTargetLanguageConverter();
1640 $text = $converter->convert( $text );
1647 $this->mOutput->getTOCData(),
1648 $this->getTargetLanguage(),
1650 $converter?->getPreferredVariant()
1653 $this->mOutput->setLanguage(
new Bcp47CodeValue(
1654 LanguageCode::bcp47( $converter->getPreferredVariant() )
1657 $this->mOutput->setLanguage( $this->getTargetLanguage() );
1660 $text = $this->mStripState->unstripNoWiki( $text );
1662 $text = $this->mStripState->unstripGeneral( $text );
1664 $text = $this->tidy->tidy( $text, Sanitizer::armorFrenchSpaces( ... ) );
1667 $this->mOutput->setTitle( $this->getPage() );
1668 $this->hookRunner->onParserAfterTidy( $this, $text );
1684 private function handleMagicLinks(
string $text ): string {
1685 $prots = $this->urlUtils->validAbsoluteProtocols();
1686 $urlChar = self::EXT_LINK_URL_CLASS;
1687 $addr = self::EXT_LINK_ADDR;
1688 $space = self::SPACE_NOT_NL; # non-newline space
1689 $spdash =
"(?:-|$space)"; # a dash or a non-newline space
1690 $spaces =
"$space++"; # possessive match of 1 or more spaces
1691 $resultText = preg_replace_callback(
1693 (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1694 (<.*?>) | # m[2]: Skip stuff inside HTML elements' .
"
1695 (\b # m[3]: Free external links
1697 ($addr$urlChar*) # m[4]: Post-protocol path
1699 \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1701 \bISBN $spaces ( # m[6]: ISBN, capture number
1702 (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1703 (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1704 [0-9Xx] # check digit
1707 $this->magicLinkCallback( ... ),
1711 if ( $resultText ===
null ) {
1712 $this->logger->warning(
"Input text contains non-valid UTF-8 characters (" . __METHOD__ .
")" );
1722 private function magicLinkCallback( array $m ): string {
1723 if ( isset( $m[1] ) && $m[1] !==
'' ) {
1726 } elseif ( isset( $m[2] ) && $m[2] !==
'' ) {
1729 } elseif ( isset( $m[3] ) && $m[3] !==
'' ) {
1730 # Free external link
1731 return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1732 } elseif ( isset( $m[5] ) && $m[5] !==
'' ) {
1734 if ( str_starts_with( $m[0],
'RFC' ) ) {
1735 if ( !$this->mOptions->getMagicRFCLinks() ) {
1740 $cssClass =
'mw-magiclink-rfc';
1741 $trackingCat =
'magiclink-tracking-rfc';
1743 } elseif ( str_starts_with( $m[0],
'PMID' ) ) {
1744 if ( !$this->mOptions->getMagicPMIDLinks() ) {
1748 $urlmsg =
'pubmedurl';
1749 $cssClass =
'mw-magiclink-pmid';
1750 $trackingCat =
'magiclink-tracking-pmid';
1754 throw new UnexpectedValueException( __METHOD__ .
': unrecognised match type "' .
1755 substr( $m[0], 0, 20 ) .
'"' );
1757 $url =
wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1758 $this->addTrackingCategory( $trackingCat );
1766 } elseif ( isset( $m[6] ) && $m[6] !==
''
1767 && $this->mOptions->getMagicISBNLinks()
1771 $space = self::SPACE_NOT_NL; # non-newline space
1772 $isbn = preg_replace(
"/$space/",
' ', $isbn );
1773 $num = strtr( $isbn, [
1778 $this->addTrackingCategory(
'magiclink-tracking-isbn' );
1780 SpecialPage::getTitleFor(
'Booksources', $num ),
1783 'class' =>
'internal mw-magiclink-isbn',
1801 private function makeFreeExternalLink(
string $url,
int $numPostProto ): string {
1804 # The characters '<' and '>' (which were escaped by
1805 # internalRemoveHtmlTags()) should not be included in
1806 # URLs, per RFC 2396.
1807 # Make terminate a URL as well (bug T84937)
1810 '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1815 $trail = substr(
$url, $m2[0][1] ) . $trail;
1816 $url = substr(
$url, 0, $m2[0][1] );
1819 # Move trailing punctuation to $trail
1821 # If there is no left bracket, then consider right brackets fair game too
1822 if ( !str_contains(
$url,
'(' ) ) {
1826 $urlRev = strrev(
$url );
1827 $numSepChars = strspn( $urlRev, $sep );
1828 # Don't break a trailing HTML entity by moving the ; into $trail
1829 # This is in hot code, so use substr_compare to avoid having to
1830 # create a new string object for the comparison
1831 if ( $numSepChars && substr_compare(
$url,
";", -$numSepChars, 1 ) === 0 ) {
1832 # more optimization: instead of running preg_match with a $
1833 # anchor, which can be slow, do the match on the reversed
1834 # string starting at the desired offset.
1835 # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1836 if ( preg_match(
'/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1840 if ( $numSepChars ) {
1841 $trail = substr(
$url, -$numSepChars ) . $trail;
1842 $url = substr(
$url, 0, -$numSepChars );
1845 # Verify that we still have a real URL after trail removal, and
1846 # not just lone protocol
1847 if ( strlen( $trail ) >= $numPostProto ) {
1848 return $url . $trail;
1851 $url = Sanitizer::cleanUrl(
$url );
1853 # Is this an external image?
1854 $text = $this->maybeMakeExternalImage(
$url );
1855 if ( $text ===
false ) {
1856 # Not an image, make a link
1859 $this->getTargetLanguageConverter()->markNoConversion(
$url ),
1862 $this->getExternalLinkAttribs(
$url )
1864 # Register it in the output object...
1865 $this->mOutput->addExternalLink(
$url );
1867 return $text . $trail;
1876 private function handleHeadings(
string $text ): string {
1877 for ( $i = 6; $i >= 1; --$i ) {
1878 $h = str_repeat(
'=', $i );
1881 $text = preg_replace(
1882 "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m",
1883 "<h$i data-mw-wikitext>\\1</h$i>",
1889 $this->
getOutput()->setExtensionData(
'core:new-heading-attr',
true );
1900 private function handleAllQuotes(
string $text ): string {
1902 $lines = StringUtils::explode(
"\n", $text );
1903 foreach ( $lines as $line ) {
1904 $outtext .= $this->doQuotes( $line ) .
"\n";
1906 $outtext = substr( $outtext, 0, -1 );
1919 $arr = preg_split(
"/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1920 $countarr = count( $arr );
1921 if ( $countarr == 1 ) {
1930 for ( $i = 1; $i < $countarr; $i += 2 ) {
1931 $thislen = strlen( $arr[$i] );
1935 if ( $thislen == 4 ) {
1936 $arr[$i - 1] .=
"'";
1939 } elseif ( $thislen > 5 ) {
1943 $arr[$i - 1] .= str_repeat(
"'", $thislen - 5 );
1948 if ( $thislen == 2 ) {
1950 } elseif ( $thislen == 3 ) {
1952 } elseif ( $thislen == 5 ) {
1962 if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1963 $firstsingleletterword = -1;
1964 $firstmultiletterword = -1;
1966 for ( $i = 1; $i < $countarr; $i += 2 ) {
1967 if ( strlen( $arr[$i] ) == 3 ) {
1968 $x1 = substr( $arr[$i - 1], -1 );
1969 $x2 = substr( $arr[$i - 1], -2, 1 );
1970 if ( $x1 ===
' ' ) {
1971 if ( $firstspace == -1 ) {
1974 } elseif ( $x2 ===
' ' ) {
1975 $firstsingleletterword = $i;
1979 } elseif ( $firstmultiletterword == -1 ) {
1980 $firstmultiletterword = $i;
1986 if ( $firstsingleletterword > -1 ) {
1987 $arr[$firstsingleletterword] =
"''";
1988 $arr[$firstsingleletterword - 1] .=
"'";
1989 } elseif ( $firstmultiletterword > -1 ) {
1991 $arr[$firstmultiletterword] =
"''";
1992 $arr[$firstmultiletterword - 1] .=
"'";
1993 } elseif ( $firstspace > -1 ) {
1997 $arr[$firstspace] =
"''";
1998 $arr[$firstspace - 1] .=
"'";
2007 foreach ( $arr as $r ) {
2008 if ( ( $i % 2 ) == 0 ) {
2009 if ( $state ===
'both' ) {
2015 $thislen = strlen( $r );
2016 if ( $thislen == 2 ) {
2018 if ( $state ===
'i' ) {
2021 } elseif ( $state ===
'bi' ) {
2024 } elseif ( $state ===
'ib' ) {
2025 $output .=
'</b></i><b>';
2027 } elseif ( $state ===
'both' ) {
2028 $output .=
'<b><i>' . $buffer .
'</i>';
2034 } elseif ( $thislen == 3 ) {
2036 if ( $state ===
'b' ) {
2039 } elseif ( $state ===
'bi' ) {
2040 $output .=
'</i></b><i>';
2042 } elseif ( $state ===
'ib' ) {
2045 } elseif ( $state ===
'both' ) {
2046 $output .=
'<i><b>' . $buffer .
'</b>';
2052 } elseif ( $thislen == 5 ) {
2054 if ( $state ===
'b' ) {
2055 $output .=
'</b><i>';
2057 } elseif ( $state ===
'i' ) {
2058 $output .=
'</i><b>';
2060 } elseif ( $state ===
'bi' ) {
2061 $output .=
'</i></b>';
2063 } elseif ( $state ===
'ib' ) {
2064 $output .=
'</b></i>';
2066 } elseif ( $state ===
'both' ) {
2067 $output .=
'<i><b>' . $buffer .
'</b></i>';
2078 if ( $state ===
'b' || $state ===
'ib' ) {
2081 if ( $state ===
'i' || $state ===
'bi' || $state ===
'ib' ) {
2084 if ( $state ===
'bi' ) {
2088 if ( $state ===
'both' && $buffer ) {
2089 $output .=
'<b><i>' . $buffer .
'</i></b>';
2103 private function handleExternalLinks(
string $text ): string {
2104 $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2105 if ( $bits ===
false ) {
2109 $s = array_shift( $bits );
2112 while ( $i < count( $bits ) ) {
2115 $text = $bits[$i++];
2116 $trail = $bits[$i++];
2118 # The characters '<' and '>' (which were escaped by
2119 # internalRemoveHtmlTags()) should not be included in
2120 # URLs, per RFC 2396.
2122 if ( preg_match(
'/&(lt|gt);/',
$url, $m2, PREG_OFFSET_CAPTURE ) ) {
2123 $text = substr(
$url, $m2[0][1] ) .
' ' . $text;
2124 $url = substr(
$url, 0, $m2[0][1] );
2127 # If the link text is an image URL, replace it with an <img> tag
2128 # This happened by accident in the original parser, but some people used it extensively
2129 $img = $this->maybeMakeExternalImage( $text );
2130 if ( $img !==
false ) {
2136 # Set linktype for CSS
2139 # No link text, e.g. [http:
2140 if ( $text ==
'' ) {
2142 $langObj = $this->getTargetLanguage();
2143 $text =
'[' . $langObj->formatNum( ++$this->mAutonumber ) .
']';
2144 $linktype =
'autonumber';
2146 # Have link text, e.g. [http:
2148 [ $dtrail, $trail ] = Linker::splitTrail( $trail );
2152 if ( preg_match(
'/^(?:' . $this->urlUtils->validAbsoluteProtocols() .
')/', $text ) ) {
2153 $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2156 $url = Sanitizer::cleanUrl(
$url );
2158 # Use the encoded URL
2159 # This means that users can paste URLs directly into the text
2160 # Funny characters like ö aren't valid in URLs anyway
2161 # This was changed in August 2004
2164 new HtmlArmor( $text ),
2167 $this->getExternalLinkAttribs(
$url )
2168 ) . $dtrail . $trail;
2170 # Register link in the output object.
2171 $this->mOutput->addExternalLink(
$url );
2191 return MediaWikiServices::getInstance()->getLinkRenderer()
2192 ->getExternalLinkRel(
$url, $title );
2208 $rel = $this->getLinkRenderer()->getExternalLinkRel(
$url, $this->getTitle() ) ??
'';
2210 $target = $this->mOptions->getExternalLinkTarget();
2212 $attribs[
'target'] = $target;
2216 if ( $rel !==
'' ) {
2217 $attribs[
'rel'] = $rel;
2233 # Test for RFC 3986 IPv6 syntax
2234 $scheme =
'[a-z][a-z0-9+.-]*:';
2235 $userinfo =
'(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2236 $ipv6Host =
'\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2237 if ( preg_match(
"<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i",
$url, $m ) &&
2238 IPUtils::isValid( rawurldecode( $m[1] ) )
2240 $isIPv6 = rawurldecode( $m[1] );
2245 # Make sure unsafe characters are encoded
2246 $url = preg_replace_callback(
2247 '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]+/',
2248 static fn ( $m ) => rawurlencode( $m[0] ),
2253 $end = strlen(
$url );
2255 # Fragment part - 'fragment'
2256 $start = strpos(
$url,
'#' );
2257 if ( $start !==
false && $start < $end ) {
2258 $ret = self::normalizeUrlComponent(
2259 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}' ) . $ret;
2263 # Query part - 'query' minus &=+;
2264 $start = strpos(
$url,
'?' );
2265 if ( $start !==
false && $start < $end ) {
2266 $ret = self::normalizeUrlComponent(
2267 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}&=+;' ) . $ret;
2271 # Path part - 'pchar', remove dot segments
2272 # (find first '/' after the optional '
2273 $start = strpos(
$url,
'//' );
2274 $start = strpos(
$url,
'/', $start ===
false ? 0 : $start + 2 );
2275 if ( $start !==
false && $start < $end ) {
2276 $ret = UrlUtils::removeDotSegments( self::normalizeUrlComponent(
2277 substr(
$url, $start, $end - $start ),
'"#%<>[\]^`{|}/?' ) ) . $ret;
2281 # Scheme and host part - 'pchar'
2282 # (we assume no userinfo or encoded colons in the host)
2283 $ret = self::normalizeUrlComponent(
2284 substr(
$url, 0, $end ),
'"#%<>[\]^`{|}/?' ) . $ret;
2287 if ( $isIPv6 !==
false ) {
2288 $ipv6Host =
"%5B({$isIPv6})%5D";
2289 $ret = preg_replace(
2290 "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2299 private static function normalizeUrlComponent(
string $component,
string $unsafe ): string {
2300 $callback = static function (
$matches ) use ( $unsafe ) {
2302 $ord = ord( $char );
2303 if ( $ord > 32 && $ord < 127 && !str_contains( $unsafe, $char ) ) {
2307 # Leave it escaped, but use uppercase for a-f
2311 return preg_replace_callback(
'/%[0-9A-Fa-f]{2}/', $callback, $component );
2322 private function maybeMakeExternalImage(
string $url ): string|false {
2323 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2324 $imagesexception = (bool)$imagesfrom;
2326 # $imagesfrom could be either a single string or an array of strings, parse out the latter
2327 if ( $imagesexception && is_array( $imagesfrom ) ) {
2328 $imagematch =
false;
2329 foreach ( $imagesfrom as $match ) {
2330 if ( str_starts_with(
$url, $match ) ) {
2335 } elseif ( $imagesexception ) {
2336 $imagematch = str_starts_with(
$url, $imagesfrom );
2338 $imagematch =
false;
2341 if ( $this->mOptions->getAllowExternalImages()
2342 || ( $imagesexception && $imagematch )
2344 if ( preg_match( self::EXT_IMAGE_REGEX,
$url ) ) {
2346 $text = Linker::makeExternalImage(
$url );
2349 if ( !$text && $this->mOptions->getEnableImageWhitelist()
2350 && preg_match( self::EXT_IMAGE_REGEX,
$url )
2352 $whitelist = explode(
2354 wfMessage(
'external_image_whitelist' )->inContentLanguage()->text()
2357 foreach ( $whitelist as $entry ) {
2358 # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2359 if ( $entry ===
'' || str_starts_with( $entry,
'#' ) ) {
2363 if ( preg_match(
'/' . str_replace(
'/',
'\\/', $entry ) .
'/i',
$url ) ) {
2364 # Image matches a whitelist entry
2365 $text = Linker::makeExternalImage(
$url );
2380 private function handleInternalLinks(
string $text ): string {
2381 $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2390 private function handleInternalLinks2( &$s ) {
2391 static $tc =
false, $e1, $e1_img;
2392 # the % is needed to support urlencoded titles as well
2394 $tc = Title::legalChars() .
'#%';
2395 # Match a link having the form [[namespace:link|alternate]]trail
2396 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2397 # Match cases where there is no "]]", which might still be images
2398 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
2401 $holders =
new LinkHolderArray(
2403 $this->getContentLanguageConverter(),
2404 $this->getHookContainer() );
2406 # split the entire text string on occurrences of [[
2407 $a = StringUtils::explode(
'[[',
' ' . $s );
2408 # get the first element (all text up to first [[), and remove the space we added
2411 $line = $a->current(); # Workaround
for broken ArrayIterator::next() that returns "
void"
2412 $s = substr( $s, 1 );
2414 $nottalk = !$this->getTitle()->isTalkPage();
2416 $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2418 if ( $useLinkPrefixExtension ) {
2419 # Match the end of a line for a word that's not followed by whitespace,
2420 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2421 $charset = $this->contLang->linkPrefixCharset();
2422 $e2 =
"/^((?>.*[^$charset]|))(.+)$/sDu";
2424 if ( preg_match( $e2, $s, $m ) ) {
2425 $first_prefix = $m[2];
2427 $first_prefix =
false;
2431 $first_prefix =
false;
2435 # Some namespaces don't allow subpages
2436 $useSubpages = $this->nsInfo->hasSubpages(
2440 # Loop for each link
2441 for ( ; $line !==
false && $line !==
null; $a->next(), $line = $a->current() ) {
2442 # Check for excessive memory usage
2443 if ( $holders->isBig() ) {
2445 # Do the existence check, replace the link holders and clear the array
2446 $holders->replace( $s );
2450 if ( $useLinkPrefixExtension ) {
2452 if ( preg_match( $e2, $s, $m ) ) {
2453 [ , $s, $prefix ] = $m;
2458 if ( $first_prefix ) {
2459 $prefix = $first_prefix;
2460 $first_prefix =
false;
2464 $might_be_img =
false;
2466 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2468 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2469 # [[Image:Foo.jpg|[http:
2470 # the real problem is with the $e1 regex
2472 # Still some problems for cases where the ] is meant to be outside punctuation,
2473 # and no image is in sight. See T4095.
2475 && substr( $m[3], 0, 1 ) ===
']'
2476 && strpos( $text,
'[' ) !==
false
2478 $text .=
']'; # so that handleExternalLinks($text) works later
2479 $m[3] = substr( $m[3], 1 );
2481 # fix up urlencoded title texts
2482 if ( str_contains( $m[1],
'%' ) ) {
2483 # Should anchors '#' also be rejected?
2484 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2487 } elseif ( preg_match( $e1_img, $line, $m ) ) {
2488 # Invalid, but might be an image with a link in its caption
2489 $might_be_img =
true;
2491 if ( str_contains( $m[1],
'%' ) ) {
2492 $m[1] = str_replace( [
'<',
'>' ], [
'<',
'>' ], rawurldecode( $m[1] ) );
2495 }
else { # Invalid form; output directly
2496 $s .= $prefix .
'[[' . $line;
2500 $origLink = ltrim( $m[1],
' ' );
2502 # Don't allow internal links to pages containing
2503 # PROTO: where PROTO is a valid URL protocol; these
2504 # should be external links.
2505 if ( preg_match(
'/^(?i:' . $this->urlUtils->validProtocols() .
')/', $origLink ) ) {
2506 $s .= $prefix .
'[[' . $line;
2510 # Make subpage if necessary
2511 if ( $useSubpages ) {
2512 $link = Linker::normalizeSubpageLink(
2513 $this->
getTitle(), $origLink, $text
2523 $unstrip = $this->mStripState->killMarkers( $link );
2524 $noMarkers = ( $unstrip === $link );
2526 $nt = $noMarkers ? Title::newFromText( $link ) : null;
2527 if ( $nt ===
null ) {
2528 $s .= $prefix .
'[[' . $line;
2532 $ns = $nt->getNamespace();
2533 $iw = $nt->getInterwiki();
2535 $noforce = !str_starts_with( $origLink,
':' );
2537 if ( $might_be_img ) { #
if this is actually an invalid link
2538 if ( $ns ===
NS_FILE && $noforce ) { # but might be an image
2541 # look at the next 'line' to see if we can close it there
2543 $next_line = $a->current();
2544 if ( $next_line ===
false || $next_line ===
null ) {
2547 $m = explode(
']]', $next_line, 3 );
2548 if ( count( $m ) == 3 ) {
2549 # the first ]] closes the inner link, the second the image
2551 $text .=
"[[{$m[0]}]]{$m[1]}";
2554 } elseif ( count( $m ) == 2 ) {
2555 # if there's exactly one ]] that's fine, we'll keep looking
2556 $text .=
"[[{$m[0]}]]{$m[1]}";
2558 # if $next_line is invalid too, we need look no further
2559 $text .=
'[[' . $next_line;
2564 # we couldn't find the end of this imageLink, so output it raw
2565 # but don't ignore what might be perfectly normal links in the text we've examined
2566 $holders->merge( $this->handleInternalLinks2( $text ) );
2567 $s .=
"{$prefix}[[$link|$text";
2568 # note: no $trail, because without an end, there *is* no trail
2571 }
else { # it
's not an image, so output it raw
2572 $s .= "{$prefix}[[$link|$text";
2573 # note: no $trail, because without an end, there *is* no trail
2578 $wasblank = ( $text == '' );
2582 # Strip off leading ':
'
2583 $text = substr( $text, 1 );
2586 # T6598 madness. Handle the quotes only if they come from the alternate part
2587 # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2588 # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2589 # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2590 $text = $this->doQuotes( $text );
2593 # Link not escaped by : , create the various objects
2594 if ( $noforce && !$nt->wasLocalInterwiki() ) {
2597 $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2598 $this->languageNameUtils->getLanguageName(
2600 LanguageNameUtils::AUTONYMS,
2601 LanguageNameUtils::DEFINED
2603 || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2606 # T26502: duplicates are resolved in ParserOutput
2607 $this->mOutput->addLanguageLink( $nt );
2613 $s = preg_replace( '/\n\s*$/
', '', $s . $prefix ) . $trail;
2617 if ( $ns === NS_FILE ) {
2619 # if no parameters were passed, $text
2620 # becomes something like "File:Foo.png",
2621 # which we don't want to pass on to the
2625 # recursively parse links inside the image caption
2626 # actually, this will parse them in any other parameters, too,
2627 # but it might be hard to fix that, and it doesn't matter ATM
2628 $text = $this->handleExternalLinks( $text );
2629 $holders->merge( $this->handleInternalLinks2( $text ) );
2631 # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2632 $s .= $prefix . $this->armorLinks(
2633 $this->makeImageInternal( $nt, $text, $holders ) ) . $trail;
2636 # Strip newlines from the left hand context of Category
2638 # See T2087, T87753, T174639, T359886
2639 $s = preg_replace(
'/\n\s*$/',
'', $s . $prefix ) . $trail;
2645 $this->mOutput->addCategory( $nt, $sortkey );
2651 # Self-link checking. For some languages, variants of the title are checked in
2652 # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2653 # for linking to a different variant.
2654 if ( $ns !==
NS_SPECIAL && $nt->equals( $this->getTitle() ) ) {
2655 $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text,
'', $trail,
'',
2656 Sanitizer::escapeIdForLink( $nt->getFragment() ) );
2660 # NS_MEDIA is a pseudo-namespace for linking directly to a file
2661 # @todo FIXME: Should do batch file existence checks, see comment below
2663 # Give extensions a chance to select the file revision for us
2666 $this->hookRunner->onBeforeParserFetchFileAndTitle(
2668 $this, $nt, $options, $descQuery
2670 # Fetch and register the file (file title may be different via hooks)
2671 [ $file, $nt ] = $this->fetchFileAndTitle( $nt, $options );
2672 # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2673 $s .= $prefix . $this->armorLinks(
2674 Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2678 # Some titles, such as valid special pages or files in foreign repos, should
2679 # be shown as bluelinks even though they're not included in the page table
2680 # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2681 # batch file existence checks for NS_FILE and NS_MEDIA
2682 if ( $iw ==
'' && $nt->isAlwaysKnown() ) {
2683 $this->mOutput->addLink( $nt );
2684 $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2686 # Links will be added to the output link list after checking
2687 $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2706 private function makeKnownLinkHolder(
2707 LinkTarget $nt,
string $text =
'',
2708 string $trail =
'',
string $prefix =
''
2710 [ $inside, $trail ] = Linker::splitTrail( $trail );
2712 if ( $text ==
'' ) {
2713 $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2717 $nt,
new HtmlArmor(
"$prefix$text$inside" )
2720 return $this->armorLinks( $link ) . $trail;
2733 private function armorLinks(
string $text ): string {
2734 return preg_replace(
'/\b((?i)' . $this->urlUtils->validProtocols() .
')/',
2735 self::MARKER_PREFIX .
"NOPARSE$1", $text );
2746 private function expandMagicVariable(
string $index, $frame =
false ): string {
2751 if ( isset( $this->mVarCache[$index] ) ) {
2752 return $this->mVarCache[$index];
2755 $value = CoreMagicVariables::expand(
2756 $this, $index,
new MWTimestamp( $this->getParseTime() ),
2757 $this->svcOptions, $this->logger, $frame
2760 if ( $value ===
null ) {
2764 $this->hookRunner->onParserGetVariableValueSwitch(
2766 $this, $fakeCache, $index, $value, $frame
2769 if ( $value ===
null ) {
2775 __METHOD__ .
" called hook for $index and got null",
2782 $this->mVarCache[$index] = $value;
2791 private function initializeVariables() {
2792 $variableIDs = $this->magicWordFactory->getVariableIDs();
2794 $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2795 $this->mSubstWords = $this->magicWordFactory->getSubstArray();
2817 return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2847 $text, $frame =
false, $argsOnly =
false, array $options = []
2849 # Is there any text? Also, Prevent too big inclusions!
2850 $textSize = strlen( $text );
2851 if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2855 if ( $frame ===
false ) {
2856 $frame = $this->getPreprocessor()->newFrame();
2857 } elseif ( !( $frame instanceof PPFrame ) ) {
2859 __METHOD__ .
" called using plain parameters instead of " .
2860 "a PPFrame instance. Creating custom frame.",
2863 $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2867 if ( $options[
'parsoidTopLevelCall'] ??
false ) {
2868 $ppFlags |= Preprocessor::START_IN_SOL_STATE;
2870 $dom = $this->preprocessToDom( $text, $ppFlags );
2871 $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2872 return $frame->
expand( $dom, $flags );
2877 $this->useParsoidFragments = $val;
2882 return $this->useParsoidFragments;
2913 # does no harm if $current and $max are present but are unnecessary for the message
2914 # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2915 # only during preview, and that would split the parser cache unnecessarily.
2916 $this->mOutput->addWarningMsg(
2917 "$limitationType-warning",
2918 Message::numParam( $current ),
2919 Message::numParam( $max )
2921 $this->addTrackingCategory(
"$limitationType-category" );
2952 $forceRawInterwiki =
false;
2954 $isChildObj =
false;
2956 $isLocalObj =
false;
2958 # Title object, where $text came from
2961 # $part1 is the bit before the first |, and must contain only title characters.
2962 # Various prefixes will be stripped from it later.
2963 $titleWithSpaces = $frame->
expand( $piece[
'title'] );
2964 $part1 = trim( $titleWithSpaces );
2967 # Original title text preserved for various purposes
2968 $originalTitle = $part1;
2970 # $args is a list of argument nodes, starting from index 0, not including $part1
2971 $args = $piece[
'parts'];
2973 $profileSection =
null;
2975 $sawDeprecatedTemplateEquals =
false;
2977 $isParsoid = $this->mOptions->getUseParsoid();
2982 $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
2983 $part1 = trim( $part1 );
2985 # Possibilities for substMatch: "subst", "safesubst" or FALSE
2986 # Decide whether to expand template or keep wikitext as-is.
2987 if ( $this->ot[
'wiki'] ) {
2988 if ( $substMatch ===
false ) {
2989 $literal =
true; # literal when in PST with no prefix
2991 $literal =
false; # expand when in PST with subst: or safesubst:
2994 if ( $substMatch ==
'subst' ) {
2995 $literal =
true; # literal when not in PST with plain subst:
2997 $literal =
false; # expand when not in PST with safesubst: or no prefix
3008 if ( !$found &&
$args->getLength() == 0 ) {
3009 $id = $this->mVariables->matchStartToEnd( $part1 );
3010 if ( $id !==
false ) {
3011 if ( str_contains( $part1,
':' ) ) {
3013 'Registering a magic variable with a name including a colon',
3014 '1.39',
false,
false
3017 $text = $this->expandMagicVariable( $id, $frame );
3022 # MSG, MSGNW and RAW
3025 $mwMsgnw = $this->magicWordFactory->get(
'msgnw' );
3026 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3029 # Remove obsolete MSG:
3030 $mwMsg = $this->magicWordFactory->get(
'msg' );
3031 $mwMsg->matchStartAndRemove( $part1 );
3035 $mwRaw = $this->magicWordFactory->get(
'raw' );
3036 if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3037 $forceRawInterwiki =
true;
3044 if ( preg_match(
'/[::]/u', $part1, $colonMatches, PREG_OFFSET_CAPTURE ) ) {
3045 [ $colonStr, $colonPos ] = $colonMatches[0];
3046 $func = substr( $part1, 0, $colonPos );
3047 $funcArgs = [ trim( substr( $part1, $colonPos + strlen( $colonStr ) ) ) ];
3048 $argsLength =
$args->getLength();
3049 for ( $i = 0; $i < $argsLength; $i++ ) {
3050 $funcArgs[] =
$args->item( $i );
3053 $result = $this->callParserFunction(
3054 $frame, $func, $funcArgs, $isParsoid && $piece[
'lineStart']
3058 if ( isset( $result[
'title'] ) ) {
3059 $title = $result[
'title'];
3061 if ( isset( $result[
'found'] ) ) {
3062 $found = $result[
'found'];
3064 if ( array_key_exists(
'text', $result ) ) {
3066 $text = $result[
'text'];
3068 if ( isset( $result[
'nowiki'] ) ) {
3069 $nowiki = $result[
'nowiki'];
3071 if ( isset( $result[
'isHTML'] ) ) {
3072 $isHTML = $result[
'isHTML'];
3074 if ( isset( $result[
'isRawHTML'] ) ) {
3075 $isRawHTML = $result[
'isRawHTML'];
3077 if ( isset( $result[
'forceRawInterwiki'] ) ) {
3078 $forceRawInterwiki = $result[
'forceRawInterwiki'];
3080 if ( isset( $result[
'isChildObj'] ) ) {
3081 $isChildObj = $result[
'isChildObj'];
3083 if ( isset( $result[
'isLocalObj'] ) ) {
3084 $isLocalObj = $result[
'isLocalObj'];
3089 # Finish mangling title and then check for loops.
3090 # Set $title to a Title object and $titleText to the PDBK
3093 # Split the title into page and subpage
3095 $relative = Linker::normalizeSubpageLink(
3096 $this->
getTitle(), $part1, $subpage
3098 if ( $part1 !== $relative ) {
3100 $ns = $this->
getTitle()->getNamespace();
3102 $title = Title::newFromText( $part1, $ns );
3104 $titleText = $title->getPrefixedText();
3105 # Check for language variants if the template is not found
3106 if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3107 $this->getTargetLanguageConverter()->findVariantLink( $part1, $title,
true );
3109 # Do recursion depth check
3110 $limit = $this->mOptions->getMaxTemplateDepth();
3111 if ( $frame->depth >= $limit ) {
3113 $text =
'<span class="error">'
3114 .
wfMessage(
'parser-template-recursion-depth-warning' )
3115 ->numParams( $limit )->inContentLanguage()->text()
3121 # Load from database
3122 if ( !$found && $title ) {
3123 $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3124 if ( !$title->isExternal() ) {
3125 if ( $title->isSpecialPage()
3126 && $this->mOptions->getAllowSpecialInclusion()
3127 && ( $this->ot[
'html'] || ( $this->useParsoidFragments && $this->ot[
'pre'] ) )
3129 $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3134 $argsLength =
$args->getLength();
3135 for ( $i = 0; $i < $argsLength; $i++ ) {
3136 $bits =
$args->item( $i )->splitArg();
3137 if ( strval( $bits[
'index'] ) ===
'' ) {
3138 $name = trim( $frame->
expand( $bits[
'name'], PPFrame::STRIP_COMMENTS ) );
3139 $value = trim( $frame->
expand( $bits[
'value'] ) );
3140 $pageArgs[$name] = $value;
3145 if ( $this->incrementExpensiveFunctionCount() ) {
3146 $context =
new RequestContext;
3147 $context->setTitle( $title );
3148 $context->setRequest(
new FauxRequest( $pageArgs ) );
3149 if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3150 $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3153 $context->setUser( User::newFromName(
'127.0.0.1',
false ) );
3155 $context->setLanguage( $this->mOptions->getUserLangObj() );
3156 $ret = $this->specialPageFactory->capturePath( $title, $context, $this->
getLinkRenderer() );
3158 $text = $context->getOutput()->getHTML();
3159 $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3162 if ( $specialPage && $specialPage->maxIncludeCacheTime() !==
false ) {
3163 $this->mOutput->updateRuntimeAdaptiveExpiry(
3164 $specialPage->maxIncludeCacheTime()
3169 } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3170 $found =
false; # access denied
3171 $this->logger->debug(
3173 ": template inclusion denied for " . $title->getPrefixedDBkey()
3176 [ $text, $title ] = $this->getTemplateDom( $title, $isParsoid && $piece[
'lineStart'] );
3177 if ( $text !==
false ) {
3182 $title->getDBkey() ===
'=' &&
3183 $originalTitle ===
'='
3189 $sawDeprecatedTemplateEquals =
true;
3194 # If the title is valid but undisplayable, make a link to it
3195 if ( !$found && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3196 $text =
"[[:$titleText]]";
3199 } elseif ( $title->isTrans() ) {
3200 # Interwiki transclusion
3201 if ( $this->ot[
'html'] && !$forceRawInterwiki ) {
3202 $text = $this->interwikiTransclude( $title,
'render' );
3205 $text = $this->interwikiTransclude( $title,
'raw' );
3206 # Preprocess it like a template
3207 $sol = ( $isParsoid && $piece[
'lineStart'] ) ? Preprocessor::START_IN_SOL_STATE : 0;
3208 $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION | $sol );
3214 # Do infinite loop check
3215 # This has to be done after redirect resolution to avoid infinite loops via redirects
3218 $text =
'<span class="error">'
3219 .
wfMessage(
'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3221 $this->addTrackingCategory(
'template-loop-category' );
3222 $this->mOutput->addWarningMsg(
3223 'template-loop-warning',
3226 $this->logger->debug( __METHOD__ .
": template loop broken at '$titleText'" );
3230 # If we haven't found text to substitute by now, we're done
3231 # Recover the source wikitext and return it
3234 if ( $profileSection ) {
3235 $this->mProfiler->scopedProfileOut( $profileSection );
3237 return [
'object' => $text ];
3240 # Expand DOM-style return values in a child frame
3241 if ( $isChildObj ) {
3242 # Clean up argument array
3243 $newFrame = $frame->
newChild( $args, $title );
3246 $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3247 } elseif ( $titleText !==
false && $newFrame->isEmpty() ) {
3248 # Expansion is eligible for the empty-frame cache
3249 $text = $newFrame->cachedExpand( $titleText, $text );
3251 # Uncached expansion
3252 $text = $newFrame->expand( $text );
3255 if ( $isLocalObj && $nowiki ) {
3256 $text = $frame->
expand( $text, PPFrame::RECOVER_ORIG );
3257 $isLocalObj =
false;
3260 if ( $profileSection ) {
3261 $this->mProfiler->scopedProfileOut( $profileSection );
3264 $sawDeprecatedTemplateEquals &&
3265 $this->mStripState->unstripBoth( $text ) !==
'='
3269 $this->addTrackingCategory(
'template-equals-category' );
3270 $this->mOutput->addWarningMsg(
'template-equals-warning' );
3273 # Replace raw HTML by a placeholder
3276 $text = $this->insertStripItem( $text );
3277 } elseif ( $isRawHTML ) {
3278 $marker = self::MARKER_PREFIX .
"-pf-"
3279 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3283 $this->mStripState->addNoWiki( $marker, $text );
3285 } elseif ( $nowiki && ( $this->ot[
'html'] || $this->ot[
'pre'] ) ) {
3286 # Escape nowiki-style return values
3289 } elseif ( is_string( $text )
3290 && !$piece[
'lineStart']
3291 && preg_match(
'/^(?:{\\||:|;|#|\*)/', $text )
3299 $text =
"\n" . $text;
3302 if ( is_string( $text ) && !$this->incrementIncludeSize(
'post-expand', strlen( $text ) ) ) {
3303 # Error, oversize inclusion
3304 if ( $titleText !==
false ) {
3305 # Make a working, properly escaped link if possible (T25588)
3306 $text =
"[[:$titleText]]";
3308 # This will probably not be a working link, but at least it may
3309 # provide some hint of where the problem is
3310 $originalTitle = preg_replace(
'/^:/',
'', $originalTitle );
3311 $text =
"[[:$originalTitle]]";
3313 $text .= $this->insertStripItem(
'<!-- WARNING: template omitted, '
3314 .
'post-expand include size too large -->' );
3315 $this->limitationWarn(
'post-expand-template-inclusion' );
3318 if ( $isLocalObj ) {
3319 $ret = [
'object' => $text ];
3321 $ret = [
'text' => $text ];
3353 # Case sensitive functions
3354 if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3355 $function = $this->mFunctionSynonyms[1][$function];
3357 # Case insensitive functions
3358 $function = $this->contLang->lc( $function );
3359 if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3360 $function = $this->mFunctionSynonyms[0][$function];
3362 return [
'found' => false ];
3366 [ $callback, $flags ] = $this->mFunctionHooks[$function];
3368 $allArgs = [ $this ];
3369 if ( $flags & self::SFH_OBJECT_ARGS ) {
3370 # Convert arguments to PPNodes and collect for appending to $allArgs
3372 foreach ( $args as $k => $v ) {
3373 if ( $v instanceof
PPNode || $k === 0 ) {
3376 $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3380 # Add a frame parameter, and pass the arguments as an array
3381 $allArgs[] = $frame;
3382 $allArgs[] = $funcArgs;
3384 # Convert arguments to plain text and append to $allArgs
3385 foreach ( $args as $k => $v ) {
3386 if ( $v instanceof
PPNode ) {
3387 $allArgs[] = trim( $frame->
expand( $v ) );
3388 } elseif ( is_int( $k ) && $k >= 0 ) {
3389 $allArgs[] = trim( $v );
3391 $allArgs[] = trim(
"$k=$v" );
3396 $result = $callback( ...$allArgs );
3398 # The interface for function hooks allows them to return a wikitext
3399 # string or an array containing the string and any flags. This mungs
3400 # things around to match what this method should return.
3401 if ( !is_array( $result ) ) {
3407 if ( isset( $result[0] ) && !isset( $result[
'text'] ) ) {
3408 $result[
'text'] = $result[0];
3410 unset( $result[0] );
3416 $noparse = $result[
'noparse'] ??
true;
3418 $preprocessFlags = $result[
'preprocessFlags'] ?? 0;
3419 if ( $inSolState ) {
3420 $preprocessFlags |= Preprocessor::START_IN_SOL_STATE;
3422 $result[
'text'] = $this->preprocessToDom( $result[
'text'], $preprocessFlags );
3423 $result[
'isChildObj'] =
true;
3448 $cacheTitle = $title;
3449 $titleKey = CacheKeyHelper::getKeyForPage( $title );
3451 if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3452 [ $ns, $dbk ] = $this->mTplRedirCache[$titleKey];
3453 $title = Title::makeTitle( $ns, $dbk );
3454 $titleKey = CacheKeyHelper::getKeyForPage( $title );
3458 $titleKey =
"$titleKey:sol=" . ( $inSolState ?
"0" :
"1" );
3459 if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3460 return [ $this->mTplDomCache[$titleKey], $title ];
3463 # Cache miss, go to the database
3466 [ $text, $title ] = $this->fetchTemplateAndTitle( $title );
3469 if ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3470 return [
false, $title ];
3473 if ( $text ===
false ) {
3474 $this->mTplDomCache[$titleKey] =
false;
3475 return [
false, $title ];
3478 $flags = Preprocessor::DOM_FOR_INCLUSION | ( $inSolState ? Preprocessor::START_IN_SOL_STATE : 0 );
3479 $dom = $this->preprocessToDom( $text, $flags );
3480 $this->mTplDomCache[$titleKey] = $dom;
3482 if ( !$title->isSameLinkAs( $cacheTitle ) ) {
3483 $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3484 [ $title->getNamespace(), $title->getDBkey() ];
3487 return [ $dom, $title ];
3504 $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3505 if ( !$this->currentRevisionCache ) {
3506 $this->currentRevisionCache =
new MapCacheLRU( 100 );
3508 if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3509 $title = Title::newFromLinkTarget( $link );
3512 $this->mOptions->getCurrentRevisionRecordCallback()(
3516 if ( $revisionRecord ===
false ) {
3519 $revisionRecord =
null;
3521 $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3523 return $this->currentRevisionCache->get( $cacheKey );
3533 $key = CacheKeyHelper::getKeyForPage( $link );
3535 $this->currentRevisionCache &&
3536 $this->currentRevisionCache->has( $key )
3562 $page = Title::newFromLinkTarget( $link );
3577 return self::defaultFetchRevisionRecord(
3578 MediaWikiServices::getInstance()->getRevisionLookup(),
3592 $title = Title::newFromLinkTarget( $link );
3595 $templateCb = $this->mOptions->getTemplateCallback();
3596 $stuff = $templateCb( $title, $this );
3597 $revRecord = $stuff[
'revision-record'] ??
null;
3599 $text = $stuff[
'text'];
3600 if ( is_string( $stuff[
'text'] ) ) {
3602 $text = strtr( $text,
"\x7f",
"?" );
3604 $finalTitle = $stuff[
'finalTitle'] ?? $title;
3605 foreach ( ( $stuff[
'deps'] ?? [] ) as $dep ) {
3606 $this->mOutput->addTemplate( $dep[
'title'], $dep[
'page_id'], $dep[
'rev_id'] );
3607 if ( $dep[
'title']->equals( $this->getTitle() ) && $revRecord instanceof
RevisionRecord ) {
3610 $sha1 = $revRecord->getSha1();
3614 $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1,
'Self transclusion' );
3615 $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3619 return [ $text, $finalTitle ];
3654 $title = Title::castFromLinkTarget( $link );
3655 $text = $skip =
false;
3657 $finalTitle = $title;
3660 $contextTitle = $parser ? $parser->getTitle() :
null;
3662 # Loop to fetch the article, with up to 2 redirects
3664 # Note that $title (including redirect targets) could be
3665 # external; we do allow hooks a chance to redirect the
3666 # external title to a local one (which might be useful), but
3667 # are careful not to add external titles to the dependency
3670 for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3671 # Give extensions a chance to select the revision instead
3672 $revRecord =
null; # Assume no hook
3673 $origTitle = $title;
3674 $titleChanged =
false;
3677 # contain fragments or even represent an attempt to transclude
3678 # a broken or otherwise-missing
Title, which the hook may
3679 # fix up. Similarly, the $contextTitle may represent a special
3680 # page or other page which
"exists" as a parsing context but
3682 $contextTitle, $title,
3688 if ( !$title->isExternal() ) {
3691 'page_id' => $title->getArticleID(),
3698 if ( !$revRecord ) {
3700 $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3706 # Update title, as $revRecord may have been changed by hook
3707 $title = Title::newFromPageIdentity( $revRecord->getPage() );
3711 'page_id' => $revRecord->getPageId(),
3712 'rev_id' => $revRecord->getId(),
3714 } elseif ( !$title->isExternal() ) {
3717 'page_id' => $title->getArticleID(),
3721 if ( !$title->equals( $origTitle ) ) {
3722 # If we fetched a rev from a different title, register
3723 # the original title too...
3724 if ( !$origTitle->isExternal() ) {
3726 'title' => $origTitle,
3727 'page_id' => $origTitle->getArticleID(),
3731 $titleChanged =
true;
3733 # If there is no current revision, there is no page
3734 if ( $revRecord ===
null || $revRecord->getId() ===
null ) {
3738 if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3743 $finalTitle = $title;
3746 if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) {
3747 $content = $revRecord->getContent( SlotRecord::MAIN );
3748 $text = $content ? $content->getWikitextForTransclusion() :
null;
3753 if ( $text ===
false || $text ===
null ) {
3758 $content = $shadowPageLoader->
get( $title )?->getContentForTransclusion();
3760 $text = $content->getWikitextForTransclusion();
3770 $finalTitle = $title;
3771 $title = $content->getRedirectTarget();
3780 'revision-record' => $revRecord ?:
false,
3782 'finalTitle' => $finalTitle,
3796 $services = MediaWikiServices::getInstance();
3797 return self::defaultFetchTemplate(
3798 $services->getRevisionLookup(),
3799 new HookRunner( $services->getHookContainer() ),
3800 $services->getLinkCache(),
3801 $services->getShadowPageLoader(),
3816 $file = $this->fetchFileNoRegister( $link, $options );
3818 $time = $file ? $file->getTimestamp() :
false;
3819 $sha1 = $file ? $file->getSha1() :
false;
3820 # Register the file as a dependency...
3821 $this->mOutput->addImage( $link, $time, $sha1 );
3822 if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3823 # Update fetched file title after resolving redirects, etc.
3824 $link = $file->getTitle();
3825 $this->mOutput->addImage( $link, $time, $sha1 );
3828 $title = Title::newFromLinkTarget( $link );
3829 return [ $file, $title ];
3843 if ( isset( $options[
'broken'] ) ) {
3846 if ( isset( $options[
'sha1'] ) ) {
3847 $file = $this->repoGroup->findFileFromKey( $options[
'sha1'], $options );
3849 $link = TitleValue::newFromLinkTarget( $link );
3850 $file = $this->repoGroup->findFile( $link, $options );
3867 return wfMessage(
'scarytranscludedisabled' )->inContentLanguage()->text();
3871 $title = Title::newFromLinkTarget( $link );
3873 $url = $title->getFullURL( [
'action' => $action ] );
3874 if ( strlen(
$url ) > 1024 ) {
3875 return wfMessage(
'scarytranscludetoolong' )->inContentLanguage()->text();
3878 $wikiId = $title->getTransWikiID();
3880 $fname = __METHOD__;
3882 $cache = $this->wanCache;
3883 $data = $cache->getWithSetCallback(
3884 $cache->makeGlobalKey(
3885 'interwiki-transclude',
3886 ( $wikiId !==
false ) ? $wikiId :
'external',
3890 function ( $oldValue, &$ttl ) use (
$url, $fname, $cache ) {
3891 $req = $this->httpRequestFactory->create(
$url, [], $fname );
3893 $status = $req->execute();
3894 if ( !$status->isOK() ) {
3895 $ttl = $cache::TTL_UNCACHEABLE;
3896 } elseif ( $req->getResponseHeader(
'X-Database-Lagged' ) !==
null ) {
3897 $ttl = min( $cache::TTL_LAGGED, $ttl );
3901 'text' => $status->isOK() ? $req->getContent() :
null,
3902 'code' => $req->getStatus()
3906 'checkKeys' => ( $wikiId !== false )
3907 ? [ $cache->makeGlobalKey(
'interwiki-page', $wikiId, $title->getDBkey() ) ]
3909 'pcGroup' =>
'interwiki-transclude:5',
3910 'pcTTL' => $cache::TTL_PROC_LONG
3914 if ( is_string( $data[
'text'] ) ) {
3915 $text = $data[
'text'];
3916 } elseif ( $data[
'code'] != 200 ) {
3918 $text =
wfMessage(
'scarytranscludefailed-httpstatus' )
3919 ->params(
$url, $data[
'code'] )->inContentLanguage()->text();
3921 $text =
wfMessage(
'scarytranscludefailed',
$url )->inContentLanguage()->text();
3938 $parts = $piece[
'parts'];
3939 $nameWithSpaces = $frame->
expand( $piece[
'title'] );
3940 $argName = trim( $nameWithSpaces );
3943 if ( $text ===
false && $parts->getLength() > 0
3944 && ( $this->ot[
'html']
3946 || ( $this->ot[
'wiki'] && $frame->
isTemplate() )
3949 # No match in frame, use the supplied default
3950 $object = $parts->item( 0 )->getChildren();
3952 if ( !$this->incrementIncludeSize(
'arg', strlen( $text ) ) ) {
3953 $error =
'<!-- WARNING: argument omitted, expansion size too large -->';
3954 $this->limitationWarn(
'post-expand-template-argument' );
3957 if ( $text ===
false && $object ===
false ) {
3961 if ( $error !==
false ) {
3964 if ( $object !==
false ) {
3965 $ret = [
'object' => $object ];
3967 $ret = [
'text' => $text ];
3975 return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
3994 static $errorStr =
'<span class="error">';
3996 $name = $frame->
expand( $params[
'name'] );
3997 if ( str_starts_with( $name, $errorStr ) ) {
4004 $attrText = !isset( $params[
'attr'] ) ?
'' : $frame->
expand( $params[
'attr'] );
4005 if ( str_starts_with( $attrText, $errorStr ) ) {
4013 $content = !isset( $params[
'inner'] ) ? null : $frame->
expand( $params[
'inner'] );
4015 $marker = self::MARKER_PREFIX .
"-$name-"
4016 . sprintf(
'%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
4018 $normalizedName = strtolower( $name );
4019 $isNowiki = $normalizedName ===
'nowiki';
4020 $markerType = $isNowiki ?
'nowiki' :
'general';
4024 $extra = $isNowiki ? ( $content ??
'' ) : null;
4026 if ( $this->ot[
'html'] || ( $isNowiki && $this->useParsoidFragments ) ) {
4027 $attributes = Sanitizer::decodeTagAttributes( $attrText );
4029 if ( isset( $params[
'attributes'] ) ) {
4030 $attributes += $params[
'attributes'];
4033 if ( isset( $this->mTagHooks[$normalizedName] ) ) {
4036 $output = $this->mTagHooks[$normalizedName]( $content, $attributes, $this, $frame );
4038 $output =
'<span class="error">Invalid tag extension name: ' .
4039 htmlspecialchars( $normalizedName ) .
'</span>';
4042 if ( is_array( $output ) ) {
4045 $output = $flags[0];
4046 if ( isset( $flags[
'isRawHTML'] ) ) {
4047 $markerType =
'nowiki';
4049 if ( isset( $flags[
'markerType'] ) ) {
4050 $markerType = $flags[
'markerType'];
4056 if ( isset( $params[
'attributes'] ) ) {
4057 foreach ( $params[
'attributes'] as $attrName => $attrValue ) {
4058 $attrText .=
' ' . htmlspecialchars( $attrName ) .
'="' .
4059 htmlspecialchars( $this->getStripState()->unstripBoth( $attrValue ), ENT_COMPAT ) .
'"';
4062 if ( $content ===
null ) {
4063 $output =
"<$name$attrText/>";
4065 $close =
$params[
'close'] ===
null ?
'' : $frame->
expand( $params[
'close'] );
4066 if ( str_starts_with( $close, $errorStr ) ) {
4070 $output =
"<$name$attrText>$content$close";
4072 if ( $this->useParsoidFragments ) {
4073 $markerType =
'exttag';
4077 if ( $markerType ===
'none' ) {
4079 } elseif ( $markerType ===
'nowiki' ) {
4080 $this->mStripState->addNoWiki( $marker, $output, $extra );
4081 } elseif ( $markerType ===
'general' ) {
4082 $this->mStripState->addGeneral( $marker, $output );
4083 } elseif ( $markerType ===
'exttag' ) {
4084 $this->mStripState->addExtTag( $marker, $output, $frame );
4086 throw new UnexpectedValueException( __METHOD__ .
': invalid marker type' );
4098 private function incrementIncludeSize( $type, $size ) {
4099 if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4102 $this->mIncludeSizes[$type] += $size;
4112 $this->mExpensiveFunctionCount++;
4113 return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4123 private function handleDoubleUnderscore(
string $text ): string {
4124 # The position of __TOC__ needs to be recorded
4125 $mw = $this->magicWordFactory->get(
'toc' );
4127 if ( $mw->match( $text ) ) {
4128 $this->mShowToc =
true;
4129 $this->mForceTocPosition =
true;
4130 # record the alias used
4131 preg_match( $mw->getRegex(), $text, $tocAlias );
4133 # Set a placeholder. At the end we'll fill it in with the TOC.
4134 $text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
4136 # Only keep the first one.
4137 $text = $mw->replace(
'', $text );
4140 # Now match and remove the rest of them
4141 $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4142 $this->mDoubleUnderscores = $mwa->matchAndRemove( $text, returnAlias: true );
4144 # For consistency with all other double-underscores (see below)
4145 $this->mDoubleUnderscores[
'toc'] = $tocAlias[0];
4148 if ( isset( $this->mDoubleUnderscores[
'nogallery'] ) ) {
4149 $this->mOutput->setNoGallery(
true );
4151 if ( isset( $this->mDoubleUnderscores[
'notoc'] ) && !$this->mForceTocPosition ) {
4152 $this->mShowToc =
false;
4154 if ( isset( $this->mDoubleUnderscores[
'hiddencat'] )
4157 $this->addTrackingCategory(
'hidden-category-category' );
4159 # (T10068) Allow control over whether robots index a page.
4160 # __NOINDEX__ always overrides __INDEX__, see T16899
4162 isset( $this->mDoubleUnderscores[
'noindex'] ) &&
4163 $this->nsInfo->canUseNoindex( $this->getPage()->getNamespace() )
4165 $this->mOutput->setIndexPolicy(
'noindex' );
4166 $this->addTrackingCategory(
'noindex-category' );
4169 isset( $this->mDoubleUnderscores[
'index'] ) &&
4170 $this->nsInfo->canUseNoindex( $this->getPage()->getNamespace() )
4172 $this->mOutput->setIndexPolicy(
'index' );
4173 $this->addTrackingCategory(
'index-category' );
4176 foreach ( $this->mDoubleUnderscores as $key => $alias ) {
4177 # Cache all double underscores in the database
4178 $this->mOutput->setUnsortedPageProperty( $key );
4179 # Check for deprecated local aliases (T407289)
4180 $ascii = str_starts_with( $alias,
'__' ) && str_ends_with( $alias,
'__' );
4181 $wide = str_starts_with( $alias,
'__' ) && str_ends_with( $alias,
'__' );
4182 if ( !( $ascii || $wide ) ) {
4183 $this->addTrackingCategory(
'bad-double-underscore-category' );
4197 return $this->trackingCategories->addTrackingCategory(
4198 $this->mOutput, $msg, $this->getPage()
4219 ->inLanguage( $this->getTargetLanguage() )
4220 ->page( $this->getPage() );
4223 private function cleanUpTocLine( Node $container ) {
4224 '@phan-var Element|DocumentFragment $container';
4227 # * <sup> and <sub> (T10393)
4231 # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4232 # * <s> and <strike> (T35715)
4234 # We strip any parameter from accepted tags, except dir="rtl|ltr" from <span>,
4235 # to allow setting directionality in toc items.
4236 $allowedTags = [
'span',
'sup',
'sub',
'bdi',
'i',
'b',
's',
'strike',
'q' ];
4237 $node = $container->firstChild;
4238 while ( $node !==
null ) {
4239 $next = $node->nextSibling;
4240 if ( $node instanceof Element ) {
4241 $nodeName = DOMUtils::nodeName( $node );
4242 if ( in_array( $nodeName, [
'style',
'script' ],
true ) ) {
4243 # Remove any <style> or <script> tags (T198618)
4244 DOMCompat::remove( $node );
4245 } elseif ( in_array( $nodeName, $allowedTags,
true ) ) {
4248 foreach ( $node->attributes as $attr ) {
4250 $nodeName ===
'span' && $attr->name ===
'dir'
4251 && ( $attr->value ===
'rtl' || $attr->value ===
'ltr' )
4256 $removeAttrs[] = $attr;
4258 foreach ( $removeAttrs as $attr ) {
4259 $node->removeAttributeNode( $attr );
4261 $this->cleanUpTocLine( $node );
4262 # Strip '<span></span>', which is the result from the above if
4263 # <span id="foo"></span> is used to produce an additional anchor
4265 if ( $nodeName ===
'span' && !$node->hasChildNodes() ) {
4266 DOMCompat::remove( $node );
4270 if ( $node->firstChild !==
null ) {
4271 $next = $node->firstChild;
4273 while ( $childNode = $node->firstChild ) {
4274 $node->parentNode->insertBefore( $childNode, $node );
4277 DOMCompat::remove( $node );
4279 } elseif ( $node instanceof Comment ) {
4282 DOMCompat::remove( $node );
4303 private function finalizeHeadings(
string $text,
string $origText,
bool $isMain =
true ): string {
4304 # Inhibit editsection links
if requested in the page
4305 if ( isset( $this->mDoubleUnderscores[
'noeditsection'] ) ) {
4306 $maybeShowEditLink =
false;
4308 $maybeShowEditLink =
true;
4311 # Get all headlines for numbering them and adding funky stuff like [edit]
4312 # links - this is for later, but we need the number of headlines right now
4313 # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4314 # be trimmed here since whitespace in HTML headings is significant.
4316 $numMatches = preg_match_all(
4317 '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4322 # if there are fewer than 4 headlines in the article, do not show TOC
4323 # unless it's been explicitly enabled.
4324 $enoughToc = $this->mShowToc &&
4325 ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4327 # Allow user to stipulate that a page should have a "new section"
4328 # link added via __NEWSECTIONLINK__
4329 if ( isset( $this->mDoubleUnderscores[
'newsectionlink'] ) ) {
4330 $this->mOutput->setNewSection(
true );
4333 # Allow user to remove the "new section"
4334 # link via __NONEWSECTIONLINK__
4335 if ( isset( $this->mDoubleUnderscores[
'nonewsectionlink'] ) ) {
4336 $this->mOutput->setHideNewSection(
true );
4339 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4340 # override above conditions and always show TOC above first header
4341 if ( isset( $this->mDoubleUnderscores[
'forcetoc'] ) ) {
4342 $this->mShowToc =
true;
4346 if ( !$numMatches ) {
4352 $haveTocEntries =
false;
4354 # Ugh .. the TOC should have neat indentation levels which can be
4355 # passed to the skin functions. These are determined here
4358 $tocData =
new TOCData();
4359 $baseTitleText = $this->
getTitle()->getPrefixedDBkey();
4360 $oldType = $this->mOutputType;
4361 $this->setOutputType( self::OT_WIKI );
4362 $frame = $this->getPreprocessor()->newFrame();
4363 $root = $this->preprocessToDom( $origText );
4364 $node = $root->getFirstChild();
4368 $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4369 $domDocument = DOMCompat::newDocument();
4370 foreach (
$matches[3] as $headline ) {
4372 $isTemplate =
false;
4374 $sectionIndex =
false;
4375 if ( preg_match( self::HEADLINE_MARKER_REGEX, $headline, $markerMatches ) ) {
4376 $serial = (int)$markerMatches[1];
4377 [ $titleText, $sectionIndex ] = $this->mHeadings[$serial];
4378 $isTemplate = ( $titleText != $baseTitleText );
4379 $headline = ltrim( substr( $headline, strlen( $markerMatches[0] ) ) );
4382 $sectionMetadata = SectionMetadata::fromLegacy( [
4383 "fromtitle" => $titleText ?: null,
4384 "index" => $sectionIndex === false
4385 ?
'' : ( ( $isTemplate ?
'T-' :
'' ) . $sectionIndex )
4387 $tocData->addSection( $sectionMetadata );
4390 $level = (int)
$matches[1][$headlineCount];
4391 $tocData->processHeading( $oldLevel, $level, $sectionMetadata );
4393 if ( $tocData->getCurrentTOCLevel() < $maxTocLevel ) {
4394 $haveTocEntries =
true;
4397 # Remove link placeholders by the link text.
4398 # <!--LINK number-->
4400 # link text with suffix
4401 # Do this before unstrip since link text can contain strip markers
4402 $fullyParsedHeadline = $this->replaceLinkHoldersText( $headline );
4404 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4405 $fullyParsedHeadline = $this->mStripState->unstripBoth( $fullyParsedHeadline );
4409 $fullyParsedHeadline = $this->tidy->tidy( $fullyParsedHeadline, Sanitizer::armorFrenchSpaces( ... ) );
4414 $wrappedHeadline =
"<h$level" .
$matches[
'attrib'][$headlineCount] . $fullyParsedHeadline .
"</h$level>";
4418 $headlineDom = DOMUtils::parseHTMLToFragment( $domDocument, $wrappedHeadline );
4422 $h = $headlineDom->firstChild;
4423 $headingId = ( $h instanceof Element && DOMUtils::isHeading( $h ) ) ?
4424 DOMCompat::getAttribute( $h,
'id' ) :
null;
4426 $this->cleanUpTocLine( $headlineDom );
4430 $tocline = trim( DOMUtils::getFragmentInnerHTML( $headlineDom ) );
4433 $headlineText = trim( $headlineDom->textContent );
4435 if ( $headingId ===
null || $headingId ===
'' ) {
4436 $headingId = Sanitizer::normalizeSectionNameWhitespace( $headlineText );
4437 $headingId = self::normalizeSectionName( $headingId );
4440 # Create the anchor for linking from the TOC to the section
4441 $fallbackAnchor = Sanitizer::escapeIdForAttribute( $headingId, Sanitizer::ID_FALLBACK );
4442 $linkAnchor = Sanitizer::escapeIdForLink( $headingId );
4443 $anchor = Sanitizer::escapeIdForAttribute( $headingId, Sanitizer::ID_PRIMARY );
4444 if ( $fallbackAnchor === $anchor ) {
4445 # No reason to have both (in fact, we can't)
4446 $fallbackAnchor =
false;
4449 # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4450 $arrayKey = strtolower( $anchor );
4451 if ( $fallbackAnchor ===
false ) {
4452 $fallbackArrayKey =
false;
4454 $fallbackArrayKey = strtolower( $fallbackAnchor );
4457 if ( isset( $refers[$arrayKey] ) ) {
4458 for ( $i = 2; isset( $refers[
"{$arrayKey}_$i"] ); ++$i );
4460 $linkAnchor .=
"_$i";
4461 $refers[
"{$arrayKey}_$i"] =
true;
4463 $refers[$arrayKey] =
true;
4465 if ( $fallbackAnchor !==
false && isset( $refers[$fallbackArrayKey] ) ) {
4466 for ( $i = 2; isset( $refers[
"{$fallbackArrayKey}_$i"] ); ++$i );
4467 $fallbackAnchor .=
"_$i";
4468 $refers[
"{$fallbackArrayKey}_$i"] =
true;
4470 $refers[$fallbackArrayKey] =
true;
4473 # Add the section to the section tree
4474 # Find the DOM node for this header
4475 $noOffset = ( $isTemplate || $sectionIndex === false );
4476 while ( $node && !$noOffset ) {
4477 if ( $node->getName() ===
'h' ) {
4478 $bits = $node->splitHeading();
4479 if ( $bits[
'i'] == $sectionIndex ) {
4483 $cpOffset += mb_strlen(
4484 $this->mStripState->unstripBoth(
4485 $frame->
expand( $node, PPFrame::RECOVER_ORIG )
4488 $node = $node->getNextSibling();
4490 $sectionMetadata->line = $tocline;
4491 $sectionMetadata->codepointOffset = ( $noOffset ? null : $cpOffset );
4492 $sectionMetadata->anchor = $anchor;
4493 $sectionMetadata->linkAnchor = $linkAnchor;
4495 if ( $maybeShowEditLink && $sectionIndex !==
false ) {
4497 if ( $isTemplate ) {
4498 # Put a T flag in the section identifier, to indicate to extractSections()
4499 # that sections inside <includeonly> should be counted.
4500 $editsectionPage = $titleText;
4501 $editsectionSection =
"T-$sectionIndex";
4503 $editsectionPage = $this->
getTitle()->getPrefixedText();
4504 $editsectionSection = $sectionIndex;
4515 $editlink =
'<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4516 $editlink .=
'" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) .
'"';
4517 $editlink .=
'>' . htmlspecialchars( $headlineText ) .
'</mw:editsection>';
4528 $head[$headlineCount] =
"<h$level" . Html::expandAttributes( [
4529 'data-mw-anchor' => $anchor,
4530 'data-mw-fallback-anchor' => $fallbackAnchor,
4531 ] ) .
$matches[
'attrib'][$headlineCount] . $headline . $editlink .
"</h$level>";
4536 $this->setOutputType( $oldType );
4538 # Never ever show TOC if no headers (or suppressed)
4539 $suppressToc = $this->mOptions->getSuppressTOC();
4540 if ( !$haveTocEntries ) {
4543 $addTOCPlaceholder =
false;
4545 if ( $isMain && !$suppressToc ) {
4553 $this->mOutput->setTOCData( $tocData );
4559 $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4560 if ( !$this->mForceTocPosition ) {
4561 $addTOCPlaceholder =
true;
4570 if ( !$this->mShowToc ) {
4571 $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
4575 # split up and insert constructed headlines
4576 $blocks = preg_split(
'/<h[1-6]\b[^>]*>.*?<\/h[1-6]>/is', $text );
4581 foreach ( $blocks as $block ) {
4583 if ( empty( $head[$i - 1] ) ) {
4584 $sections[$i] = $block;
4586 $sections[$i] = $head[$i - 1] . $block;
4592 if ( $addTOCPlaceholder ) {
4596 $sections[0] .= self::TOC_PLACEHOLDER .
"\n";
4599 return implode(
'', $sections );
4615 ?
string $preferredVariant =
null
4617 if ( $tocData ===
null ) {
4620 foreach ( $tocData->getSections() as $s ) {
4627 $s->line, $preferredVariant,
false
4632 $pieces = explode( $dot, $s->number );
4634 foreach ( $pieces as $i => $p ) {
4640 $s->number = $numbering;
4663 if ( $clearState ) {
4664 $magicScopeVariable = $this->lock();
4666 $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4667 $this->setUser( $user );
4670 $text = str_replace(
"\000",
'', $text );
4675 $text = TextContent::normalizeLineEndings( $text );
4678 $text = $this->pstPass2( $text, $user );
4680 $text = $this->mStripState->unstripBoth( $text );
4683 $text = rtrim( $text );
4685 $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4687 $this->setUser(
null ); # Reset
4700 private function pstPass2(
string $text, UserIdentity $user ): string {
4701 # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4702 # $this->contLang here in order to give everyone the same signature and use the default one
4703 # rather than the one selected in each user
's preferences. (see also T14815)
4704 $ts = $this->mOptions->getTimestamp();
4705 $timestamp = MWTimestamp::getLocalInstance( $ts );
4706 $ts = $timestamp->format( 'YmdHis
' );
4707 $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4709 $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4711 # Variable replacement
4712 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4713 $text = $this->replaceVariables( $text );
4715 # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4716 # which may corrupt this parser instance via its wfMessage()->text() call-
4719 if ( str_contains( $text, '~~~
' ) ) {
4720 $sigText = $this->getUserSig( $user );
4721 $text = strtr( $text, [
4723 '~~~~
' => "$sigText $d",
4726 # The main two signature forms used above are time-sensitive
4727 $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE, 'User signature detected
' );
4730 # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4731 $tc = '[
' . Title::legalChars() . ']
';
4732 $nc = '[ _0-9A-Za-z\x80-\xff-]
'; # Namespaces can use non-ascii!
4734 // [[ns:page (context)|]]
4735 $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4736 // [[ns:page(context)|]] (double-width brackets, added in r40257)
4737 $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4738 // [[ns:page (context), context|]] (using single, double-width or Arabic comma)
4739 $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4740 // [[|page]] (reverse pipe trick: add context from page title)
4741 $p2 = "/\[\[\\|($tc+)]]/";
4743 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4744 $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]
', $text );
4745 $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]
', $text );
4746 $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]
', $text );
4748 $t = $this->getTitle()->getText();
4750 if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4751 $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4752 } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4753 $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4755 # if there's no context, don
't bother duplicating the title
4756 $text = preg_replace( $p2, '[[\\1]]
', $text );
4777 public function getUserSig( UserIdentity $user, $nickname = false, $fancySig = null ): string {
4778 $username = $user->getName();
4780 # If not given, retrieve from the user object.
4781 if ( $nickname === false ) {
4782 $nickname = $this->userOptionsLookup->getOption( $user, 'nickname
' );
4785 $fancySig ??= $this->userOptionsLookup->getBoolOption( $user, 'fancysig
' );
4787 if ( $nickname === null || $nickname === '' ) {
4788 // Empty value results in the default signature (even when fancysig is enabled)
4789 $nickname = $username;
4790 } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4791 $nickname = $username;
4792 $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
4793 } elseif ( $fancySig !== false ) {
4794 # Sig. might contain markup; validate this
4795 $isValid = $this->validateSig( $nickname ) !== false;
4798 $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4799 if ( $isValid && $sigValidation === 'disallow
' ) {
4800 $parserOpts = new ParserOptions(
4801 $this->mOptions->getUserIdentity(),
4804 $validator = $this->signatureValidatorFactory
4805 ->newSignatureValidator( $user, null, $parserOpts );
4806 $isValid = !$validator->validateSignature( $nickname );
4810 # Validated; clean up (if needed) and return it
4811 return $this->cleanSig( $nickname, true );
4813 # Failed to validate; fall back to the default
4814 $nickname = $username;
4815 $this->logger->debug( __METHOD__ . ": $username has invalid signature." );
4819 # Make sure nickname doesn't get a sig in a sig
4820 $nickname = self::cleanSigInSig( $nickname );
4822 # 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';
4833 return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4834 ->page( $this->getPage() )->text();
4845 return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4859 public function cleanSig( $text, $parsing =
false ): string {
4861 $magicScopeVariable = $this->lock();
4864 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
4865 self::OT_PREPROCESS,
4870 # Option to disable this feature
4871 if ( !$this->mOptions->getCleanSignatures() ) {
4875 # @todo FIXME: Regex doesn't respect extension tags or nowiki
4876 # => Move this logic to braceSubstitution()
4877 $substWord = $this->magicWordFactory->get(
'subst' );
4878 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
4879 $substText =
'{{' . $substWord->getSynonym( 0 );
4881 $text = preg_replace( $substRegex, $substText, $text );
4882 $text = self::cleanSigInSig( $text );
4883 $dom = $this->preprocessToDom( $text );
4884 $frame = $this->getPreprocessor()->newFrame();
4885 $text = $frame->
expand( $dom );
4888 $text = $this->mStripState->unstripBoth( $text );
4902 $text = preg_replace(
'/~{3,5}/',
'', $text );
4926 if ( !str_contains( $text,
'mw:PageProp/toc' ) ) {
4931 return HtmlHelper::modifyElements(
4933 static function ( SerializerNode $node ): bool {
4934 $prop = $node->attrs[
'property'] ??
'';
4935 return $node->name ===
'meta' && $prop ===
'mw:PageProp/toc';
4937 static function ( SerializerNode $node ) use ( &$replaced, $toc ) {
4963 $outputType, $clearState =
true, $revId =
null
4965 $this->startParse( $page, $options, $outputType, $clearState );
4966 if ( $revId !==
null ) {
4967 $this->mRevisionId = $revId;
4978 $outputType, $clearState =
true
4980 $this->setPage( $page );
4981 $this->mOptions = $options;
4982 $this->setOutputType( $outputType );
4983 if ( $clearState ) {
4984 $this->clearState();
4998 static $executing = false;
5000 # Guard against infinite recursion
5006 $text = $this->preprocess( $text, $page ?? $this->mTitle, $options );
5031 public function setHook( $tag, callable $callback ) {
5032 $tag = strtolower( $tag );
5033 if ( preg_match(
'/[<>\r\n]/', $tag, $m ) ) {
5034 throw new InvalidArgumentException(
"Invalid character {$m[0]} in setHook('$tag', ...) call" );
5036 $oldVal = $this->mTagHooks[$tag] ??
null;
5037 $this->mTagHooks[$tag] = $callback;
5038 if ( !in_array( $tag, $this->mStripList ) ) {
5039 $this->mStripList[] = $tag;
5050 $this->mTagHooks = [];
5051 $this->mStripList = [];
5103 $oldVal = $this->mFunctionHooks[$id][0] ?? null;
5104 $this->mFunctionHooks[$id] = [ $callback, $flags ];
5106 # Add to function cache
5107 $mw = $this->magicWordFactory->get( $id );
5109 $synonyms = $mw->getSynonyms();
5110 $sensitive = intval( $mw->isCaseSensitive() );
5112 foreach ( $synonyms as $syn ) {
5114 if ( !$sensitive ) {
5115 $syn = $this->contLang->lc( $syn );
5118 if ( !( $flags & self::SFH_NO_HASH ) ) {
5121 # Remove trailing colon (or Japanese double-width colon)
5122 if ( str_ends_with( $syn,
':' ) || str_ends_with( $syn,
':' ) ) {
5123 $syn = mb_substr( $syn, 0, -1 );
5125 $this->mFunctionSynonyms[$sensitive][$syn] = $id;
5137 return array_keys( $this->mFunctionHooks );
5146 private function replaceLinkHoldersPrivate(
string &$text ): void {
5147 $this->mLinkHolders->replace( $text );
5157 private function replaceLinkHoldersText(
string $text ): string {
5158 return $this->mLinkHolders->replaceText( $text );
5176 $mode = $params[
'mode'] ?? false;
5179 $ig = ImageGalleryBase::factory( $mode );
5182 $ig = ImageGalleryBase::factory();
5185 $ig->setContextTitle( $this->getTitle() );
5186 $ig->setShowBytes(
false );
5187 $ig->setShowDimensions(
false );
5188 $ig->setParser( $this );
5189 $ig->setHideBadImages();
5190 $ig->setAttributes( Sanitizer::validateTagAttributes( $params,
'ul' ) );
5192 $ig->setShowFilename( isset( $params[
'showfilename'] ) );
5193 if ( isset( $params[
'caption'] ) ) {
5197 $caption = $this->recursiveTagParse( $params[
'caption'] );
5198 $ig->setCaptionHtml( $caption );
5200 if ( isset( $params[
'perrow'] ) ) {
5201 $ig->setPerRow( $params[
'perrow'] );
5203 if ( isset( $params[
'widths'] ) ) {
5204 $ig->setWidths( $params[
'widths'] );
5206 if ( isset( $params[
'heights'] ) ) {
5207 $ig->setHeights( $params[
'heights'] );
5209 $ig->setAdditionalOptions( $params );
5211 $lines = StringUtils::explode(
"\n", $text );
5212 foreach ( $lines as $line ) {
5213 # match lines like these:
5214 # Image:someimage.jpg|This is some image
5216 preg_match(
"/^([^|]+)(\\|(.*))?$/", $line,
$matches );
5222 if ( str_contains(
$matches[0],
'%' ) ) {
5226 if ( $title ===
null ) {
5227 # Bogus title. Ignore these so we don't bomb out later.
5231 # We need to get what handler the file uses, to figure out parameters.
5232 # Note, a hook can override the file name, and chose an entirely different
5233 # file (which potentially could be of a different type and have different handler).
5236 $this->hookRunner->onBeforeParserFetchFileAndTitle(
5238 $this, $title, $options, $descQuery
5240 # Don't register it now, as TraditionalImageGallery does that later.
5241 $file = $this->fetchFileNoRegister( $title, $options );
5242 $handler = $file ? $file->getHandler() :
false;
5245 'img_alt' =>
'gallery-internal-alt',
5246 'img_link' =>
'gallery-internal-link',
5249 $paramMap += $handler->getParamMap();
5252 unset( $paramMap[
'img_width'] );
5255 $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5259 $handlerOptions = [];
5272 $parameterMatches = StringUtils::delimiterExplode(
5279 foreach ( $parameterMatches as $parameterMatch ) {
5280 [ $magicName, $match ] = $mwArray->matchVariableStartToEnd( trim( $parameterMatch ) );
5281 if ( !$magicName ) {
5283 $label = $parameterMatch;
5287 $paramName = $paramMap[$magicName];
5288 switch ( $paramName ) {
5289 case 'gallery-internal-alt':
5291 $alt = $this->stripAltText( $match );
5293 case 'gallery-internal-link':
5294 $linkValue = $this->stripAltText( $match );
5295 if ( preg_match(
'/^-{R\|(.*)}-$/', $linkValue ) ) {
5298 $linkValue = substr( $linkValue, 4, -2 );
5300 [ $type, $target ] = $this->parseLinkParameter( $linkValue );
5302 if ( $type ===
'no-link' ) {
5305 $imageOptions[$type] = $target;
5310 if ( $handler->validateParam( $paramName, $match ) ) {
5311 $handlerOptions[$paramName] = $match;
5314 $this->logger->debug(
5315 "$parameterMatch failed parameter validation" );
5316 $label = $parameterMatch;
5323 if ( !$hasAlt && $label !==
'' ) {
5324 $alt = $this->stripAltText( $label );
5326 $imageOptions[
'title'] = $this->stripAltText( $label );
5329 $handlerOptions[
'targetlang'] = $this->getTargetLanguage()->getCode();
5332 $title, $label, $alt,
'', $handlerOptions,
5333 ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5336 $html = $ig->toHTML();
5337 $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5345 private function getImageParams( $handler ) {
5346 $handlerClass = $handler ? get_class( $handler ) :
'';
5347 if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5348 # Initialise static lists
5349 static $internalParamNames = [
5350 'horizAlign' => [
'left',
'right',
'center',
'none' ],
5351 'vertAlign' => [
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
5352 'bottom',
'text-bottom' ],
5353 'frame' => [
'thumbnail',
'framed',
'frameless',
'border',
5356 'manualthumb',
'upright',
'link',
'alt',
'class' ],
5358 static $internalParamMap;
5359 if ( !$internalParamMap ) {
5360 $internalParamMap = [];
5361 foreach ( $internalParamNames as $type => $names ) {
5362 foreach ( $names as $name ) {
5368 $magicName = str_replace(
'-',
'_',
"img_$name" );
5369 $internalParamMap[$magicName] = [ $type, $name ];
5374 # Add handler params
5375 # Since img_width is one of these, it is important it is listed
5376 # *after* the literal parameter names above (T372935).
5377 $paramMap = $internalParamMap;
5379 $handlerParamMap = $handler->getParamMap();
5380 foreach ( $handlerParamMap as $magic => $paramName ) {
5381 $paramMap[$magic] = [
'handler', $paramName ];
5385 $paramMap[
'img_width' ] = [
'handler',
'width' ];
5387 $this->mImageParams[$handlerClass] = $paramMap;
5388 $this->mImageParamsMagicArray[$handlerClass] =
5389 $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5391 return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5403 return $this->makeImageInternal(
5404 $link, $options, shouldReplaceLinkHolders: true
5422 public function makeImage( LinkTarget $link, $options, $holders =
false ): string {
5424 return $this->makeImageInternal(
5425 $link, $options, $holders ?: null, shouldReplaceLinkHolders: false
5438 private function makeImageInternal(
5442 bool $shouldReplaceLinkHolders =
false
5444 # Check
if the options text is of the form
"options|alt text"
5446 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5447 # * left no resizing, just left align. label is used for alt= only
5448 # * right same, but right aligned
5449 # * none same, but not aligned
5450 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5451 # * center center the image
5452 # * framed Keep original image size, no magnify-button.
5453 # * frameless like
'thumb' but without a frame. Keeps user preferences for width
5454 # * upright reduce width for upright images, rounded to full __0 px
5455 # * border draw a 1px border around the image
5456 # * alt Text for HTML alt attribute (defaults to empty)
5457 # * class Set a class for img node
5458 # * link Set the target of the image link. Can be external, interwiki, or local
5459 # vertical-align values (no % or length right now):
5469 #
Protect LanguageConverter markup when splitting into parts
5471 '-{',
'}-',
'|', $options, true
5474 # Give extensions a chance to select the file revision for us
5477 $title = Title::castFromLinkTarget( $link );
5478 $this->hookRunner->onBeforeParserFetchFileAndTitle(
5480 $this, $title, $options, $descQuery
5482 # Fetch and register the file (file title may be different via hooks)
5483 [ $file, $link ] = $this->fetchFileAndTitle( $link, $options );
5486 $handler = $file ? $file->getHandler() :
false;
5488 [ $paramMap, $mwArray ] = $this->getImageParams( $handler );
5491 $this->addTrackingCategory(
'broken-file-category' );
5494 # Process the input parameters
5496 $params = [
'frame' => [],
'handler' => [],
5497 'horizAlign' => [],
'vertAlign' => [] ];
5498 $seenformat =
false;
5499 foreach ( $parts as $part ) {
5500 [ $magicName, $value ] = $mwArray->matchVariableStartToEnd( trim( $part ) );
5502 if ( isset( $paramMap[$magicName] ) ) {
5503 [ $type, $paramName ] = $paramMap[$magicName];
5505 # Special case; width and height come in one variable together
5506 if ( $type ===
'handler' && $paramName ===
'width' ) {
5508 $parsedWidthParam = $this->parseWidthParam( $value,
true,
true );
5511 $validateFunc =
static function ( $name, $value ) use ( $handler ) {
5513 ? $handler->validateParam( $name, $value )
5516 if ( isset( $parsedWidthParam[
'width'] ) ) {
5517 $width = $parsedWidthParam[
'width'];
5518 if ( $validateFunc(
'width', $width ) ) {
5519 $params[$type][
'width'] = $width;
5523 if ( isset( $parsedWidthParam[
'height'] ) ) {
5524 $height = $parsedWidthParam[
'height'];
5525 if ( $validateFunc(
'height', $height ) ) {
5526 $params[$type][
'height'] = $height;
5530 # else no validation -- T15436
5532 if ( $type ===
'handler' ) {
5533 # Validate handler parameter
5534 $validated = $handler->validateParam( $paramName, $value );
5536 # Validate internal parameters
5537 switch ( $paramName ) {
5541 $value = $this->stripAltText( $value, $holders );
5544 [ $paramName, $value ] =
5545 $this->parseLinkParameter(
5546 $this->stripAltText( $value, $holders )
5550 if ( $paramName ===
'no-link' ) {
5556 # @todo FIXME: Possibly check validity here for
5557 # manualthumb? downstream behavior seems odd with
5558 # missing manual thumbs.
5559 $value = $this->stripAltText( $value, $holders );
5565 $validated = !$seenformat;
5569 # Most other things appear to be empty or numeric...
5570 $validated = ( $value ===
false || is_numeric( trim( $value ) ) );
5575 $params[$type][$paramName] = $value;
5579 if ( !$validated ) {
5584 # Process alignment parameters
5585 if ( $params[
'horizAlign'] !== [] ) {
5586 $params[
'frame'][
'align'] = array_key_first( $params[
'horizAlign'] );
5588 if ( $params[
'vertAlign'] !== [] ) {
5589 $params[
'frame'][
'valign'] = array_key_first( $params[
'vertAlign'] );
5592 $params[
'frame'][
'caption'] = $caption;
5594 # Will the image be presented in a frame, with the caption below?
5596 $hasVisibleCaption = isset( $params[
'frame'][
'framed'] )
5597 || isset( $params[
'frame'][
'thumbnail'] )
5598 || isset( $params[
'frame'][
'manualthumb'] );
5600 # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5601 # came to also set the caption, ordinary text after the image -- which
5602 # makes no sense, because that just repeats the text multiple times in
5603 # screen readers. It *also* came to set the title attribute.
5604 # Now that we have an alt attribute, we should not set the alt text to
5605 # equal the caption: that's worse than useless, it just repeats the
5606 # text. This is the framed/thumbnail case. If there's no caption, we
5607 # use the unnamed parameter for alt text as well, just for the time be-
5608 # ing, if the unnamed param is set and the alt param is not.
5609 # For the future, we need to figure out if we want to tweak this more,
5610 # e.g., introducing a title= parameter for the title; ignoring the un-
5611 # named parameter entirely for images without a caption; adding an ex-
5612 # plicit caption= parameter and preserving the old magic unnamed para-
5615 if ( !$hasVisibleCaption ) {
5617 if ( !isset( $params[
'frame'][
'alt'] ) && $caption !==
'' ) {
5618 # No alt text, use the "caption" for the alt text
5619 $params[
'frame'][
'alt'] = $this->stripAltText( $caption, $holders );
5621 # Use the "caption" for the tooltip text
5622 $params[
'frame'][
'title'] = $this->stripAltText( $caption, $holders );
5624 $params[
'handler'][
'targetlang'] = $this->getTargetLanguage()->getCode();
5627 $title = Title::castFromLinkTarget( $link );
5628 $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5630 # Linker does the rest
5631 $time = $options[
'time'] ??
false;
5632 $params[
'handler'][
'requestProvenance'] =
'parser';
5633 $ret = Linker::makeImageLink( $this, $link, $file, $params[
'frame'], $params[
'handler'],
5634 $time, $descQuery, $this->mOptions->getThumbSize() );
5636 # Give the handler a chance to modify the parser object
5638 $handler->parserTransformHook( $this, $file );
5641 $this->modifyImageHtml( $file, $params, $ret );
5643 if ( $shouldReplaceLinkHolders ) {
5644 $this->replaceLinkHoldersPrivate( $ret );
5668 private function parseLinkParameter( $value ) {
5669 $chars = self::EXT_LINK_URL_CLASS;
5670 $addr = self::EXT_LINK_ADDR;
5671 $prots = $this->urlUtils->validProtocols();
5674 if ( $value ===
'' ) {
5676 } elseif ( preg_match(
"/^((?i)$prots)/", $value ) ) {
5677 if ( preg_match(
"/^((?i)$prots)$addr$chars*$/u", $value ) ) {
5678 $this->mOutput->addExternalLink( $value );
5708 if ( str_contains( $value,
'%' ) ) {
5709 $value = rawurldecode( $value );
5711 $linkTitle = Title::newFromText( $value );
5713 $this->mOutput->addLink( $linkTitle );
5714 $type =
'link-title';
5715 $target = $linkTitle;
5718 return [ $type, $target ];
5729 $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5732 private function stripAltText(
string $caption, ?
LinkHolderArray $holders =
null ): string {
5733 # Strip bad stuff out of the title (tooltip). We can
't just use
5734 # replaceLinkHoldersText() here, because if this function is called
5735 # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5736 if ( $holders !== null ) {
5737 $tooltip = $holders->replaceText( $caption );
5739 $tooltip = $this->replaceLinkHoldersText( $caption );
5742 # make sure there are no placeholders in thumbnail attributes
5743 # that are later expanded to html- so expand them now and
5745 $tooltip = $this->mStripState->unstripBoth( $tooltip );
5746 # Compatibility hack! In HTML certain entity references not terminated
5747 # by a semicolon are decoded (but not if we're in an attribute; that's
5748 # how link URLs get away without properly escaping & in queries).
5749 # But wikitext has always required semicolon-termination of entities,
5750 # so encode & where needed to avoid decode of semicolon-less entities.
5753 # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5754 $tooltip = preg_replace(
"/
5755 & # 1. entity prefix
5756 (?= # 2. followed by:
5757 (?: # a. one of the legacy semicolon-less named entities
5758 A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5759 C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5760 GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5761 O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5762 U(?:acute|circ|grave|uml)|Yacute|
5763 a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5764 c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5765 divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5766 frac(?:1(?:2|4)|34)|
5767 gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5768 i(?:acute|circ|excl|grave|quest|uml)|laquo|
5769 lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5770 m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5771 not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5772 o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5773 p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5774 s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5775 u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5777 (?:[^;]|$)) # b. and not followed by a semicolon
5778 # S = study, for efficiency
5779 /Sx",
'&', $tooltip );
5780 $tooltip = Sanitizer::stripAllTags( $tooltip );
5792 return array_keys( $this->mTagHooks );
5800 return $this->mFunctionSynonyms;
5808 return $this->urlUtils->validProtocols();
5842 private function extractSections(
5843 string $text,
string|
int $sectionId,
string $mode,
5846 $magicScopeVariable = $this->lock();
5849 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
5854 $frame = $this->getPreprocessor()->newFrame();
5856 # Process section extraction flags
5858 $sectionParts = explode(
'-', $sectionId );
5862 $sectionIndex = (int)array_pop( $sectionParts );
5863 foreach ( $sectionParts as $part ) {
5864 if ( $part ===
'T' ) {
5865 $flags |= Preprocessor::DOM_FOR_INCLUSION;
5869 # Check for empty input
5870 if ( $text ===
'' ) {
5871 # Only sections 0 and T-0 exist in an empty document
5872 if ( $sectionIndex === 0 ) {
5873 return $mode ===
'get' ?
'' : $newText;
5875 return $mode ===
'get' ? $newText : $text;
5879 # Preprocess the text
5880 $root = $this->preprocessToDom( $text, $flags );
5882 # <h> nodes indicate section breaks
5883 # They can only occur at the top level, so we can find them by iterating the root's children
5884 $node = $root->getFirstChild();
5886 # Find the target section
5887 if ( $sectionIndex === 0 ) {
5888 # Section zero doesn't nest, level=big
5889 $targetLevel = 1000;
5892 if ( $node->getName() ===
'h' ) {
5893 $bits = $node->splitHeading();
5894 if ( $bits[
'i'] == $sectionIndex ) {
5895 $targetLevel = $bits[
'level'];
5899 if ( $mode ===
'replace' ) {
5900 $outText .= $frame->
expand( $node, PPFrame::RECOVER_ORIG );
5902 $node = $node->getNextSibling();
5908 return $mode ===
'get' ? $newText : $text;
5911 # Find the end of the section, including nested sections
5913 if ( $node->getName() ===
'h' ) {
5914 $bits = $node->splitHeading();
5915 $curLevel = $bits[
'level'];
5917 if ( $bits[
'i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5921 if ( $mode ===
'get' ) {
5922 $outText .= $frame->
expand( $node, PPFrame::RECOVER_ORIG );
5924 $node = $node->getNextSibling();
5927 # Write out the remainder (in replace mode only)
5928 if ( $mode ===
'replace' ) {
5929 # Output the replacement text.
5930 # Add two newlines. Trailing whitespace in $newText is conventionally
5931 # stripped by the editor, so we need both newlines to restore the paragraph gap.
5932 # Only add trailing whitespace if there is newText.
5933 if ( $newText !=
"" ) {
5934 $outText .= $newText .
"\n\n";
5938 $outText .= $frame->
expand( $node, PPFrame::RECOVER_ORIG );
5939 $node = $node->getNextSibling();
5943 # Re-insert stripped tags
5944 $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5965 public function getSection( $text, $sectionId, $defaultText =
'' ): string|false {
5966 return $this->extractSections( $text, $sectionId,
'get', $defaultText );
5983 return $this->extractSections( $oldText, $sectionId,
'replace', $newText );
6016 $magicScopeVariable = $this->lock();
6019 ParserOptions::newFromUser( RequestContext::getMain()->getUser() ),
6023 $frame = $this->getPreprocessor()->newFrame();
6024 $root = $this->preprocessToDom( $text, 0 );
6025 $node = $root->getFirstChild();
6037 $nodeText = $frame->
expand( $node, PPFrame::RECOVER_ORIG );
6038 if ( $node->getName() ===
'h' ) {
6039 $bits = $node->splitHeading();
6040 $sections[] = $currentSection;
6042 'index' => $bits[
'i'],
6043 'level' => $bits[
'level'],
6044 'offset' => $offset,
6045 'heading' => $nodeText,
6049 $currentSection[
'text'] .= $nodeText;
6051 $offset += strlen( $nodeText );
6052 $node = $node->getNextSibling();
6054 $sections[] = $currentSection;
6070 return $this->mRevisionId;
6080 if ( $this->mRevisionRecordObject ) {
6081 return $this->mRevisionRecordObject;
6083 if ( $this->mOptions->isMessage() ) {
6094 $rev = $this->mOptions->getCurrentRevisionRecordCallback()(
6107 if ( $this->mRevisionId ===
null && $rev->getId() ) {
6119 if ( $this->mRevisionId && $rev->getId() != $this->mRevisionId ) {
6120 $rev = MediaWikiServices::getInstance()
6121 ->getRevisionLookup()
6122 ->getRevisionById( $this->mRevisionId );
6125 $this->mRevisionRecordObject = $rev;
6127 return $this->mRevisionRecordObject;
6137 if ( $this->mRevisionTimestamp !== null ) {
6138 return $this->mRevisionTimestamp;
6141 # Use specified revision timestamp, falling back to the current timestamp
6142 $revObject = $this->getRevisionRecordObject();
6143 $timestamp = $revObject && $revObject->getTimestamp()
6144 ? $revObject->getTimestamp()
6145 : $this->mOptions->getTimestamp();
6146 $this->mOutput->setRevisionTimestampUsed( $timestamp );
6148 # The cryptic '' timezone parameter tells to use the site-default
6149 # timezone offset instead of the user settings.
6150 # Since this value will be saved into the parser cache, served
6151 # to other users, and potentially even used inside links and such,
6152 # it needs to be consistent for all visitors.
6153 $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp,
'' );
6155 return $this->mRevisionTimestamp;
6165 if ( $this->mRevisionUser === null ) {
6166 $revObject = $this->getRevisionRecordObject();
6168 # if this template is subst: the revision id will be blank,
6169 # so just use the current user's name
6170 if ( $revObject && $revObject->getUser() ) {
6171 $this->mRevisionUser = $revObject->getUser()->getName();
6172 } elseif ( $this->ot[
'wiki'] || $this->mOptions->getIsPreview() ) {
6173 $this->mRevisionUser = $this->getUserIdentity()->getName();
6175 # Note that we fall through here with
6176 # $this->mRevisionUser still null
6179 return $this->mRevisionUser;
6189 if ( $this->mRevisionSize ===
null ) {
6190 $revObject = $this->getRevisionRecordObject();
6192 # if this variable is subst: the revision id will be blank,
6193 # so just use the parser input size, because the own substitution
6194 # will change the size.
6195 $this->mRevisionSize = $revObject ? $revObject->getSize() : $this->mInputSize;
6197 return $this->mRevisionSize;
6200 private static function getSectionNameFromStrippedText(
string $text ): string {
6201 $text =
Sanitizer::normalizeSectionNameWhitespace( $text );
6202 $text = Sanitizer::decodeCharReferences( $text );
6203 $text = self::normalizeSectionName( $text );
6207 private static function makeAnchor(
string $sectionName ): string {
6208 return '#' . Sanitizer::escapeIdForLink( $sectionName );
6211 private function makeLegacyAnchor(
string $sectionName ): string {
6212 $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6213 if ( isset( $fragmentMode[1] ) && $fragmentMode[1] ===
'legacy' ) {
6215 $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
6217 $id = Sanitizer::escapeIdForLink( $sectionName );
6233 # Strip out wikitext links(they break the anchor)
6234 $text = $this->stripSectionName( $text );
6235 $sectionName = self::getSectionNameFromStrippedText( $text );
6236 return self::makeAnchor( $sectionName );
6252 # Strip out wikitext links(they break the anchor)
6253 $text = $this->stripSectionName( $text );
6254 $sectionName = self::getSectionNameFromStrippedText( $text );
6255 return $this->makeLegacyAnchor( $sectionName );
6265 $sectionName = self::getSectionNameFromStrippedText( $text );
6266 return self::makeAnchor( $sectionName );
6275 private static function normalizeSectionName(
string $text ): string {
6276 # T90902: ensure the same normalization is applied for IDs as to links
6279 $parts = $titleParser->splitTitleString(
"#$text" );
6280 }
catch ( MalformedTitleException ) {
6283 return $parts[
'fragment'];
6302 # Strip internal link markup
6303 $text = preg_replace(
'/\[\[:?([^[|]+)\|([^[]+)\]\]/',
'$2', $text );
6304 $text = preg_replace(
'/\[\[:?([^[]+)\|?\]\]/',
'$1', $text );
6306 # Strip external link markup
6307 # @todo FIXME: Not tolerant to blank link text
6309 # on how many empty links there are on the page - need to figure that out.
6310 $text = preg_replace(
6311 '/\[(?i:' . $this->urlUtils->validProtocols() .
')([^ ]+?) ([^[]+)\]/',
'$2', $text );
6313 # Parse wikitext quotes (italics & bold)
6314 $text = $this->doQuotes( $text );
6317 $text = StringUtils::delimiterReplace(
'<',
'>',
'', $text );
6342 while ( $i < strlen( $s ) ) {
6343 $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6344 if ( $markerStart ===
false ) {
6345 $out .= $callback( substr( $s, $i ) );
6348 $out .= $callback( substr( $s, $i, $markerStart - $i ) );
6349 $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6350 if ( $markerEnd ===
false ) {
6351 $out .= substr( $s, $markerStart );
6354 $markerEnd += strlen( self::MARKER_SUFFIX );
6355 $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6371 return $this->mStripState->killMarkers( $text );
6388 $parsedWidthParam = [];
6389 if ( $value ===
'' ) {
6390 return $parsedWidthParam;
6393 if ( !$localized ) {
6395 $mwArray = $this->magicWordFactory->newArray( [
'img_width' ] );
6396 [ $magicWord, $newValue ] = $mwArray->matchVariableStartToEnd( $value );
6397 $value = $magicWord ? $newValue : $value;
6400 # (T15500) In both cases (width/height and width only),
6401 # permit trailing "px" for backward compatibility.
6402 if ( $parseHeight && preg_match(
'/^([0-9]*)x([0-9]*)\s*(px)?\s*$/', $value, $m ) ) {
6403 $parsedWidthParam[
'width'] = intval( $m[1] );
6404 $parsedWidthParam[
'height'] = intval( $m[2] );
6405 if ( $m[3] ??
false ) {
6406 $this->addTrackingCategory(
'double-px-category' );
6408 } elseif ( preg_match(
'/^([0-9]*)\s*(px)?\s*$/', $value, $m ) ) {
6409 $parsedWidthParam[
'width'] = intval( $m[1] );
6410 if ( $m[2] ??
false ) {
6411 $this->addTrackingCategory(
'double-px-category' );
6414 return $parsedWidthParam;
6424 protected function lock(): ScopedCallback {
6425 if ( $this->mInParse ) {
6426 $message =
'Parser state cleared while parsing. Did you call Parser::parse recursively?';
6427 $xdebugMode = ini_get(
'xdebug.mode' );
6428 if ( $xdebugMode !==
false && str_contains( $xdebugMode,
'develop' ) ) {
6429 $message .= PHP_EOL .
'xdebug.mode=develop is known to cause this issue ' .
6430 '(xdebug bug #2222); consider ';
6431 $improvedMode = implode(
',',
6432 array_diff( explode(
',', $xdebugMode ),
6434 if ( $improvedMode !==
'' ) {
6435 $message .=
"using xdebug.mode=$improvedMode instead or ";
6437 $message .=
"disabling xdebug.";
6439 $message .= PHP_EOL .
'lock is held by: ' . $this->mInParse;
6440 throw new LogicException( $message );
6445 $e =
new RuntimeException;
6446 $this->mInParse = $e->getTraceAsString();
6448 $recursiveCheck =
new ScopedCallback(
function () {
6449 $this->mInParse =
false;
6452 return $recursiveCheck;
6463 return (
bool)$this->mInParse;
6478 if ( preg_match(
'/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && !str_contains( $m[1],
'</p>' ) ) {
6499 if ( $nsText !==
'' ) {
6500 $html .=
'<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) .
'</span>';
6501 $html .=
'<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) .
'</span>';
6503 $html .=
'<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) .
'</span>';
6504 if ( $titleLang !==
null ) {
6505 $html = Html::rawElement(
'span', [
6506 'lang' => $titleLang->getHtmlCode(),
6507 'dir' => $titleLang->getDir(),
6520 $posStart = strpos( $text,
'<body' );
6521 if ( $posStart ===
false ) {
6524 $posStart = strpos( $text,
'>', $posStart );
6525 if ( $posStart ===
false ) {
6530 $posEnd = strrpos( $text,
'</body>', $posStart );
6531 if ( $posEnd ===
false ) {
6533 return substr( $text, $posStart );
6535 return substr( $text, $posStart, $posEnd - $posStart );
6545 private function setOutputFlag( ParserOutputFlags|
string $flag,
string $reason ): void {
6546 $this->mOutput->setOutputFlag( $flag );
6547 if ( $flag instanceof ParserOutputFlags ) {
6549 $flag = $flag->value;
6551 $name = $this->
getTitle()->getPrefixedText();
6552 $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, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], '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'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], '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', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, '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, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', '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', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', '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', 'RestSandboxSpecs' => 'object', 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], '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', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], '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.', ],]