137 use DeprecationHelper;
138 use ProtectedHookAccessorTrait;
188 private $mContextTitle =
null;
194 private $action =
'submit';
203 private $isNew =
false;
206 private $deletedSinceEdit;
221 private $mTokenOk =
false;
224 private $mTriedSave =
false;
227 private $incompleteForm =
false;
230 private $missingSummary =
false;
233 private $allowBlankSummary =
false;
239 private $allowBlankArticle =
false;
242 private $problematicRedirectTarget =
null;
245 private $allowedProblematicRedirectTarget =
null;
248 private $ignoreProblematicRedirects =
false;
251 private $autoSumm =
'';
254 private $hookError =
'';
257 private $mParserOutput;
264 private $mExpectedParentRevision =
false;
278 private $diff =
false;
281 private $minoredit =
false;
284 private $watchthis =
false;
287 private $watchlistExpiryEnabled;
292 private $watchlistExpiry;
295 private $recreate =
false;
306 private $textbox2 =
'';
315 private $nosummary =
false;
334 private $editRevId =
null;
343 private $newSectionAnchor =
null;
363 private $parentRevId = 0;
366 private $scrolltop =
null;
369 private $markAsBot =
true;
378 private $changeTags =
null;
380 # Placeholders for text injection by hooks (must be HTML)
381 # extensions should take care to _append_ to the present value
405 private $undoAfter = 0;
414 private $contentLength =
false;
419 private $enableApiEditOverride =
false;
429 private $isOldRev =
false;
434 private $unicodeCheck;
437 private $editConflictHelperFactory =
null;
454 private $placeholderTempUser;
457 private $unsavedTempUser;
460 private $savedTempUser;
463 private $tempUserCreateActive =
false;
466 private $tempUserName;
469 private $tempUserCreateDone =
false;
472 private $unableToAcquireTempName =
false;
475 private LinkBatchFactory $linkBatchFactory;
484 $this->mArticle = $article;
485 $this->page = $article->
getPage();
486 $this->mTitle = $article->
getTitle();
494 $this->context->setWikiPage( $this->page );
495 $this->context->setTitle( $this->mTitle );
497 $this->contentModel = $this->mTitle->getContentModel();
501 $this->contentFormat = $this->contentHandlerFactory
502 ->getContentHandler( $this->contentModel )
503 ->getDefaultFormat();
506 $this->watchlistExpiryEnabled = $this->
getContext()->getConfig() instanceof
Config
523 $this->deprecatePublicProperty(
'textbox2',
'1.44', __CLASS__ );
524 $this->deprecatePublicProperty(
'action',
'1.38', __CLASS__ );
531 return $this->mArticle;
547 return $this->mTitle;
554 $this->mContextTitle = $title;
562 if ( $this->mContextTitle ===
null ) {
563 throw new RuntimeException(
"EditPage does not have a context title set" );
565 return $this->mContextTitle;
576 private function isSupportedContentModel(
string $modelId ): bool {
577 return $this->enableApiEditOverride === true ||
578 $this->contentHandlerFactory->getContentHandler( $modelId )->supportsDirectEditing();
589 $this->enableApiEditOverride = $enableOverride;
605 if ( !$this->getHookRunner()->onAlternateEdit( $this ) ) {
609 wfDebug( __METHOD__ .
": enter" );
611 $request = $this->context->getRequest();
613 if ( $request->
getBool(
'redlink' ) && $this->mTitle->exists() ) {
614 $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
618 $this->importFormData( $request );
619 $this->firsttime =
false;
622 if ( $this->save && $readOnlyMode->isReadOnly() ) {
625 $this->preview =
true;
629 $this->formtype =
'save';
630 } elseif ( $this->preview ) {
631 $this->formtype =
'preview';
632 } elseif ( $this->diff ) {
633 $this->formtype =
'diff';
634 }
else { # First time through
635 $this->firsttime =
true;
636 if ( $this->previewOnOpen() ) {
637 $this->formtype =
'preview';
639 $this->formtype =
'initial';
647 $this->unableToAcquireTempName = !$this->maybeActivateTempUserCreate( !$this->firsttime )->isOK();
649 $status = $this->getEditPermissionStatus(
650 $this->save ? PermissionManager::RIGOR_SECURE : PermissionManager::RIGOR_FULL
652 if ( !$status->isGood() ) {
653 wfDebug( __METHOD__ .
": User can't edit" );
655 $user = $this->context->getUser();
656 if ( $user->getBlock() && !$readOnlyMode->isReadOnly() ) {
658 $user->scheduleSpreadBlock();
660 $this->displayPermissionStatus( $status );
665 $revRecord = $this->mArticle->fetchRevisionRecord();
668 $revContentModel = $revRecord ?
669 $revRecord->getMainContentModel() :
671 if ( $revContentModel && $revContentModel !== $this->contentModel ) {
672 $prevRevRecord =
null;
673 $prevContentModel =
false;
674 if ( $this->undidRev ) {
675 $undidRevRecord = $this->revisionStore
676 ->getRevisionById( $this->undidRev );
677 $prevRevRecord = $undidRevRecord ?
678 $this->revisionStore->getPreviousRevision( $undidRevRecord ) :
681 $prevContentModel = $prevRevRecord ?
682 $prevRevRecord->getMainContentModel() :
686 if ( !$this->undidRev
688 || $prevContentModel !== $this->contentModel
690 $this->displayViewSourcePage(
691 $this->getContentObject(),
693 'contentmodelediterror',
702 $this->isConflict =
false;
704 # Attempt submission here. This will check for edit conflicts,
705 # and redundantly check for locked database, blocked IPs, etc.
706 # that edit() already checked just in case someone tries to sneak
707 # in the back door with a hand-edited submission URL.
709 if ( $this->formtype ===
'save' ) {
710 $resultDetails =
null;
711 $status = $this->attemptSave( $resultDetails );
712 if ( !$this->handleStatus( $status, $resultDetails ) ) {
717 # First time through: get contents, set time for conflict
719 if ( $this->formtype ===
'initial' || $this->firsttime ) {
720 if ( !$this->initialiseForm() ) {
724 if ( $this->mTitle->getArticleID() ) {
725 $this->getHookRunner()->onEditFormInitialText( $this );
735 $curRevisionRecord = $this->page->getRevisionRecord();
736 if ( $curRevisionRecord
738 && $curRevisionRecord->getId() !== $revRecord->getId()
739 && ( WikiPage::hasDifferencesOutsideMainSlot(
742 ) || !$this->isSupportedContentModel(
749 $restoreLink = $this->mTitle->getFullURL(
751 'action' =>
'mcrrestore',
752 'restore' => $revRecord->getId(),
755 $this->displayViewSourcePage(
756 $this->getContentObject(),
758 'nonmain-slot-differences-therefore-readonly',
765 $this->showEditForm();
778 if ( $this->tempUserCreateActive ) {
780 return Status::newGood();
782 $user = $this->context->getUser();
787 if ( $user->isTemp() ) {
788 $expiryAfterDays = $this->tempUserCreator->getExpireAfterDays();
789 if ( $expiryAfterDays ) {
790 $expirationCutoff = (int)ConvertibleTimestamp::now( TS::UNIX ) - ( 86_400 * $expiryAfterDays );
796 $firstUserRegistration = $this->userRegistrationLookup->getFirstRegistration( $user );
798 $firstUserRegistration &&
799 ConvertibleTimestamp::convert( TS::UNIX, $firstUserRegistration ) < $expirationCutoff
805 $session = $this->context->getRequest()->getSession();
806 $session->set(
'TempUser:name',
null );
810 $this->sessionManager->invalidateSessionsForUser(
811 $this->userFactory->newFromUserIdentity( $user )
817 if ( $this->tempUserCreator->shouldAutoCreate( $user,
'edit' ) ) {
819 $name = $this->tempUserCreator->acquireAndStashName(
820 $this->context->getRequest()->getSession() );
821 if ( $name ===
null ) {
822 $status = Status::newFatal(
'temp-user-unable-to-acquire' );
823 $status->value = self::AS_UNABLE_TO_ACQUIRE_TEMP_ACCOUNT;
826 $this->unsavedTempUser = $this->userFactory->newUnsavedTempUser( $name );
827 $this->tempUserName = $name;
829 $this->placeholderTempUser = $this->userFactory->newTempPlaceholder();
831 $this->tempUserCreateActive =
true;
833 return Status::newGood();
843 private function createTempUser(): Status {
844 if ( !$this->tempUserCreateActive ) {
845 return Status::newGood();
847 $request = $this->context->getRequest();
848 $status = $this->tempUserCreator->create(
852 if ( $status->isOK() ) {
853 $this->placeholderTempUser =
null;
854 $this->unsavedTempUser =
null;
855 $this->savedTempUser = $status->getUser();
856 $this->authManager->setRequestContextUserFromSessionUser();
857 $this->tempUserCreateDone =
true;
859 LoggerFactory::getInstance(
'authevents' )->info(
860 'Temporary account creation attempt: {user}',
862 'user' => $this->tempUserName,
863 'success' => $status->isOK(),
879 return $this->getUserForPermissions();
888 private function getUserForPermissions() {
889 if ( $this->savedTempUser ) {
890 return $this->savedTempUser;
891 } elseif ( $this->unsavedTempUser ) {
892 return $this->unsavedTempUser;
893 } elseif ( $this->placeholderTempUser ) {
894 return $this->placeholderTempUser;
896 return $this->context->getUser();
906 private function getUserForPreview() {
907 if ( $this->savedTempUser ) {
908 return $this->savedTempUser;
909 } elseif ( $this->unsavedTempUser ) {
910 return $this->unsavedTempUser;
911 } elseif ( $this->firsttime && $this->placeholderTempUser ) {
915 return $this->placeholderTempUser;
916 } elseif ( $this->tempUserCreateActive ) {
917 throw new BadMethodCallException(
918 "Can't use the request user for preview with IP masking enabled" );
920 return $this->context->getUser();
930 private function getUserForSave() {
931 if ( $this->savedTempUser ) {
932 return $this->savedTempUser;
933 } elseif ( $this->tempUserCreateActive ) {
934 throw new BadMethodCallException(
935 "Can't use the request user for storage with IP masking enabled" );
937 return $this->context->getUser();
945 private function getEditPermissionStatus(
string $rigor = PermissionManager::RIGOR_SECURE ): PermissionStatus {
946 $user = $this->getUserForPermissions();
947 return $this->permManager->getPermissionStatus(
966 private function displayPermissionStatus( PermissionStatus $status ): void {
968 if ( $this->context->getRequest()->getBool(
'redlink' ) ) {
972 $out->redirect( $this->mTitle->getFullURL() );
976 $content = $this->getContentObject();
980 if ( !$content || ( $this->firsttime && !$this->mTitle->exists() && $content->isEmpty() ) ) {
981 $action = $this->mTitle->exists() ?
'edit' :
982 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
983 throw new PermissionsError( $action, $status );
986 $this->displayViewSourcePage(
988 $out->formatPermissionStatus( $status,
'edit' )
997 private function displayViewSourcePage( Content $content,
string $errorMessage ): void {
999 $this->getHookRunner()->onEditPage__showReadOnlyForm_initial( $this, $out );
1001 $out->setRobotPolicy(
'noindex,nofollow' );
1002 $out->setPageTitleMsg( $this->context->msg(
1005 $this->getContextTitle()->getPrefixedText()
1007 $out->addBacklinkSubtitle( $this->getContextTitle() );
1008 $out->addHTML( $this->editFormPageTop );
1009 $out->addHTML( $this->editFormTextTop );
1011 if ( $errorMessage !==
'' ) {
1012 $out->addWikiTextAsInterface( $errorMessage );
1013 $out->addHTML(
"<hr />\n" );
1016 # If the user made changes, preserve them when showing the markup
1017 # (This happens when a user is blocked during edit, for instance)
1018 if ( !$this->firsttime ) {
1019 $text = $this->textbox1;
1020 $out->addWikiMsg(
'viewyourtext' );
1023 $text = $this->toEditText( $content );
1024 }
catch ( MWException ) {
1025 # Serialize using the default format if the content model is not supported
1026 # (e.g. for an old revision with a different model)
1027 $text = $content->serialize();
1029 $out->addWikiMsg(
'viewsourcetext' );
1032 $out->addHTML( $this->editFormTextBeforeContent );
1033 $this->showTextbox( $text,
'wpTextbox1', [
'readonly' ] );
1034 $out->addHTML( $this->editFormTextAfterContent );
1036 $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
1038 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
1040 $out->addHTML( $this->editFormTextBottom );
1041 if ( $this->mTitle->exists() ) {
1042 $out->returnToMain(
null, $this->mTitle );
1052 $config = $this->context->getConfig();
1053 $previewOnOpenNamespaces = $config->get( MainConfigNames::PreviewOnOpenNamespaces );
1054 $request = $this->context->getRequest();
1055 if ( $config->get( MainConfigNames::RawHtml ) ) {
1061 $preview = $request->
getRawVal(
'preview' );
1062 if ( $preview ===
'yes' ) {
1065 } elseif ( $preview ===
'no' ) {
1068 } elseif ( $this->section ===
'new' ) {
1071 } elseif ( ( $request->
getCheck(
'preload' ) || $this->mTitle->exists() )
1072 && $this->userOptionsLookup->getOption( $this->context->getUser(),
'previewonfirst' )
1076 } elseif ( !$this->mTitle->exists()
1077 && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
1078 && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
1093 private function isSectionEditSupported(): bool {
1094 $currentRev = $this->page->getRevisionRecord();
1097 $revContentModel = $currentRev
1098 ? $currentRev->getMainContentModel()
1099 : $this->page->getContentModel();
1102 ( $this->mArticle->getRevIdFetched() === $this->page->getLatest() ) &&
1103 $this->contentHandlerFactory->getContentHandler( $revContentModel )->supportsSections()
1113 # Section edit can come from either the form or a link
1114 $this->section = $request->
getVal(
'wpSection', $request->
getVal(
'section',
'' ) );
1116 if ( $this->section !==
null && $this->section !==
'' && !$this->isSectionEditSupported() ) {
1117 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
1120 $this->isNew = !$this->mTitle->exists() || $this->section ===
'new';
1123 $this->importFormDataPosted( $request );
1125 # Not a posted form? Start with nothing.
1126 wfDebug( __METHOD__ .
": Not a posted form." );
1127 $this->textbox1 =
'';
1128 $this->summary =
'';
1129 $this->sectiontitle =
null;
1130 $this->edittime =
'';
1131 $this->editRevId =
null;
1133 $this->edit =
false;
1134 $this->preview =
false;
1135 $this->save =
false;
1136 $this->diff =
false;
1137 $this->minoredit =
false;
1139 $this->watchthis = $request->
getBool(
'watchthis',
false );
1140 if ( $this->watchlistExpiryEnabled ) {
1141 $this->watchlistExpiry =
null;
1143 $this->recreate =
false;
1147 if ( $this->section ===
'new' && $request->
getCheck(
'preloadtitle' ) ) {
1148 $this->sectiontitle = $request->
getVal(
'preloadtitle' );
1149 $this->setNewSectionSummary();
1150 } elseif ( $this->section !==
'new' && $request->
getRawVal(
'summary' ) !==
'' ) {
1151 $this->summary = $request->
getText(
'summary' );
1152 if ( $this->summary !==
'' ) {
1156 $this->autoSumm = md5(
'' );
1160 if ( $request->
getVal(
'minor' ) ) {
1161 $this->minoredit =
true;
1165 $this->oldid = $request->
getInt(
'oldid' );
1166 $this->parentRevId = $request->
getInt(
'parentRevId' );
1168 $this->markAsBot = $request->
getBool(
'bot',
true );
1169 $this->nosummary = $request->
getBool(
'nosummary' );
1172 $this->contentModel = $request->
getText(
'model', $this->contentModel );
1174 $this->contentFormat = $request->
getText(
'format', $this->contentFormat );
1177 $handler = $this->contentHandlerFactory->getContentHandler( $this->contentModel );
1180 'editpage-invalidcontentmodel-title',
1181 'editpage-invalidcontentmodel-text',
1186 if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1188 'editpage-notsupportedcontentformat-title',
1189 'editpage-notsupportedcontentformat-text',
1192 wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1198 $this->getHookRunner()->onEditPage__importFormData( $this, $request );
1201 private function importFormDataPosted(
WebRequest $request ): void {
1202 # These fields need to be checked for encoding.
1203 # Also remove trailing whitespace, but don
't remove _initial_
1204 # whitespace from the text boxes. This may be significant formatting.
1205 $this->textbox1 = rtrim( $request->getText( 'wpTextbox1
' ) );
1206 if ( !$request->getCheck( 'wpTextbox2
' ) ) {
1207 // Skip this if wpTextbox2 has input, it indicates that we came
1208 // from a conflict page with raw page text, not a custom form
1209 // modified by subclasses
1210 $textbox1 = $this->importContentFormData( $request );
1211 if ( $textbox1 !== null ) {
1212 $this->textbox1 = $textbox1;
1216 $this->unicodeCheck = $request->getText( 'wpUnicodeCheck
' );
1218 if ( $this->section === 'new
' ) {
1219 # Allow setting sectiontitle different from the edit summary.
1220 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
1221 # currently doing double duty as both edit summary and section title. Right now this
1222 # is just to allow API edits to work around this limitation, but this should be
1223 # incorporated into the actual edit form when EditPage is rewritten (T20654, T28312).
1224 if ( $request->getCheck( 'wpSectionTitle
' ) ) {
1225 $this->sectiontitle = $request->getText( 'wpSectionTitle
' );
1226 if ( $request->getCheck( 'wpSummary
' ) ) {
1227 $this->summary = $request->getText( 'wpSummary
' );
1230 $this->sectiontitle = $request->getText( 'wpSummary
' );
1233 $this->sectiontitle = null;
1234 $this->summary = $request->getText( 'wpSummary
' );
1237 # If the summary consists of a heading, e.g. '==Foobar==
', extract the title from the
1238 # header syntax, e.g. 'Foobar
'. This is mainly an issue when we are using wpSummary for
1239 # section titles. (T3600)
1240 # It is weird to modify 'sectiontitle
', even when it is provided when using the API, but API
1241 # users have come to rely on it: https://github.com/wikimedia-gadgets/twinkle/issues/1625
1242 $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/
', '$1
', $this->summary );
1243 if ( $this->sectiontitle !== null ) {
1244 $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/
', '$1
', $this->sectiontitle );
1247 // @phan-suppress-next-line PhanSuspiciousValueComparison
1248 if ( $this->section === 'new
' ) {
1249 $this->setNewSectionSummary();
1252 $this->edittime = $request->getVal( 'wpEdittime
' );
1253 $this->editRevId = $request->getIntOrNull( 'editRevId
' );
1254 $this->starttime = $request->getVal( 'wpStarttime
' );
1256 $undidRev = $request->getInt( 'wpUndidRevision
' );
1258 $this->undidRev = $undidRev;
1260 $undoAfter = $request->getInt( 'wpUndoAfter
' );
1262 $this->undoAfter = $undoAfter;
1265 $this->scrolltop = $request->getIntOrNull( 'wpScrolltop
' );
1267 if ( $this->textbox1 === '' && !$request->getCheck( 'wpTextbox1
' ) ) {
1268 // wpTextbox1 field is missing, possibly due to being "too big"
1269 // according to some filter rules that may have been configured
1270 // for security reasons.
1271 $this->incompleteForm = true;
1273 // If we receive the last parameter of the request, we can fairly
1274 // claim the POST request has not been truncated.
1275 $this->incompleteForm = !$request->getVal( 'wpUltimateParam
' );
1277 if ( $this->incompleteForm ) {
1278 # If the form is incomplete, force to preview.
1279 wfDebug( __METHOD__ . ": Form data appears to be incomplete" );
1280 wfDebug( "POST DATA: " . var_export( $request->getPostValues(), true ) );
1281 $this->preview = true;
1283 $this->preview = $request->getCheck( 'wpPreview
' );
1284 $this->diff = $request->getCheck( 'wpDiff
' );
1286 // Remember whether a save was requested, so we can indicate
1287 // if we forced preview due to session failure.
1288 $this->mTriedSave = !$this->preview;
1290 if ( $this->tokenOk( $request ) ) {
1291 # Some browsers will not report any submit button
1292 # if the user hits enter in the comment box.
1293 # The unmarked state will be assumed to be a save,
1294 # if the form seems otherwise complete.
1295 wfDebug( __METHOD__ . ": Passed token check." );
1296 } elseif ( $this->diff ) {
1297 # Failed token check, but only requested "Show Changes".
1298 wfDebug( __METHOD__ . ": Failed token check; Show Changes requested." );
1300 # Page might be a hack attempt posted from
1301 # an external site. Preview instead of saving.
1302 wfDebug( __METHOD__ . ": Failed token check; forcing preview" );
1303 $this->preview = true;
1306 $this->save = !$this->preview && !$this->diff;
1307 if ( !$this->edittime || !preg_match( '/^\d{14}$/
', $this->edittime ) ) {
1308 $this->edittime = null;
1311 if ( !$this->starttime || !preg_match( '/^\d{14}$/
', $this->starttime ) ) {
1312 $this->starttime = null;
1315 $this->recreate = $request->getCheck( 'wpRecreate
' );
1317 $user = $this->context->getUser();
1319 $this->minoredit = $request->getCheck( 'wpMinoredit
' );
1320 $this->watchthis = $request->getCheck( 'wpWatchthis
' );
1321 $submittedExpiry = $request->getText( 'wpWatchlistExpiry
' );
1322 if ( $this->watchlistExpiryEnabled && $submittedExpiry !== '' ) {
1323 // This parsing of the user-posted expiry is done for both preview and saving. This
1324 // is necessary because ApiEditPage uses preview when it saves (yuck!). Note that it
1325 // only works because the unnormalized value is retrieved again below in
1326 // getCheckboxesDefinitionForWatchlist().
1327 $submittedExpiry = ExpiryDef::normalizeExpiry( $submittedExpiry, TS::ISO_8601 );
1328 if ( $submittedExpiry !== false ) {
1329 $this->watchlistExpiry = $submittedExpiry;
1333 # Don't force edit summaries when a user is editing their own user or talk page
1334 if ( ( $this->mTitle->getNamespace() ===
NS_USER || $this->mTitle->getNamespace() ===
NS_USER_TALK )
1335 && $this->mTitle->getText() === $user->getName()
1337 $this->allowBlankSummary =
true;
1339 $this->allowBlankSummary = $request->
getBool(
'wpIgnoreBlankSummary' )
1340 || !$this->userOptionsLookup->getOption( $user,
'forceeditsummary' );
1343 $this->autoSumm = $request->
getText(
'wpAutoSummary' );
1345 $this->allowBlankArticle = $request->
getBool(
'wpIgnoreBlankArticle' );
1346 $allowedProblematicRedirectTargetText = $request->
getText(
'wpAllowedProblematicRedirectTarget' );
1347 $this->allowedProblematicRedirectTarget = $allowedProblematicRedirectTargetText ===
''
1348 ? null : Title::newFromText( $allowedProblematicRedirectTargetText );
1349 $this->ignoreProblematicRedirects = $request->
getBool(
'wpIgnoreProblematicRedirects' );
1351 $changeTags = $request->
getVal(
'wpChangeTags' );
1352 $changeTagsAfterPreview = $request->
getVal(
'wpChangeTagsAfterPreview' );
1353 if ( $changeTags ===
null || $changeTags ===
'' ) {
1354 $this->changeTags = [];
1356 $this->changeTags = array_filter(
1359 explode(
',', $changeTags )
1363 if ( $changeTagsAfterPreview !==
null && $changeTagsAfterPreview !==
'' ) {
1364 $this->changeTags = array_merge( $this->changeTags, array_filter(
1367 explode(
',', $changeTagsAfterPreview )
1391 private function initialiseForm(): bool {
1392 $this->edittime = $this->page->getTimestamp();
1393 $this->editRevId = $this->page->getLatest();
1395 $dummy = $this->contentHandlerFactory
1396 ->getContentHandler( $this->contentModel )
1397 ->makeEmptyContent();
1398 $content = $this->getContentObject( $dummy ); # TODO: track content
object?!
1399 if ( $content === $dummy ) {
1400 $this->noSuchSectionPage();
1405 $out = $this->context->getOutput();
1407 $this->editFormPageTop .= Html::errorBox(
1408 $out->parseAsInterface( $this->context->msg(
'missing-revision-content',
1410 Message::plaintextParam( $this->mTitle->getPrefixedText() )
1413 } elseif ( !$this->isSupportedContentModel( $content->getModel() ) ) {
1414 $modelMsg = $this->getContext()->msg(
'content-model-' . $content->getModel() );
1415 $modelName = $modelMsg->exists() ? $modelMsg->text() : $content->getModel();
1417 $out = $this->context->getOutput();
1418 $out->showErrorPage(
1419 'modeleditnotsupported-title',
1420 'modeleditnotsupported-text',
1426 $this->textbox1 = $this->toEditText( $content );
1428 $user = $this->context->getUser();
1430 # Sort out the "watch" checkbox
1431 if ( $this->userOptionsLookup->getOption( $user,
'watchdefault' ) ) {
1433 $this->watchthis =
true;
1434 } elseif ( $this->userOptionsLookup->getOption( $user,
'watchcreations' ) && !$this->mTitle->exists() ) {
1436 $this->watchthis =
true;
1437 } elseif ( $this->watchlistManager->isWatched( $user, $this->mTitle ) ) {
1439 $this->watchthis =
true;
1441 if ( $this->watchthis && $this->watchlistExpiryEnabled ) {
1442 $watchedItem = $this->watchedItemStore->getWatchedItem( $user, $this->getTitle() );
1443 $this->watchlistExpiry = $watchedItem ? $watchedItem->getExpiry() :
null;
1445 if ( !$this->isNew && $this->userOptionsLookup->getOption( $user,
'minordefault' ) ) {
1446 $this->minoredit =
true;
1448 if ( $this->textbox1 ===
false ) {
1460 $services = MediaWikiServices::getInstance();
1461 $request = $this->context->getRequest();
1466 if ( !$this->mTitle->exists() || $this->section ===
'new' ) {
1468 $this->mTitle->toPageIdentity(),
1469 $this->context->getUser(),
1470 $request->
getVal(
'preload' ),
1471 $request->
getArray(
'preloadparams', [] ),
1472 $request->
getVal(
'section' )
1475 } elseif ( $this->section !==
'' ) {
1477 $orig = $this->getOriginalContent( $this->getAuthority() );
1478 $content = $orig ? $orig->getSection( $this->section ) :
null;
1481 $content = $defaultContent;
1484 $undoafter = $request->
getInt(
'undoafter' );
1485 $undo = $request->
getInt(
'undo' );
1487 if ( $undo > 0 && $undoafter > 0 ) {
1490 $undorev = $this->revisionStore->getRevisionByTitle( $this->mTitle, $undo );
1491 $oldrev = $this->revisionStore->getRevisionByTitle( $this->mTitle, $undoafter );
1494 # Make sure it's the right page,
1495 # the revisions exist and they were not deleted.
1496 # Otherwise, $content will be left as-is.
1497 if ( $undorev !==
null && $oldrev !==
null &&
1498 !$undorev->
isDeleted( RevisionRecord::DELETED_TEXT ) &&
1499 !$oldrev->
isDeleted( RevisionRecord::DELETED_TEXT )
1501 if ( WikiPage::hasDifferencesOutsideMainSlot( $undorev, $oldrev )
1502 || !$this->isSupportedContentModel(
1507 $this->context->getOutput()->redirect( $this->mTitle->getFullURL( [
1508 'action' =>
'mcrundo',
1510 'undoafter' => $undoafter,
1514 $content = $this->getUndoContent( $undorev, $oldrev, $undoMsg );
1517 if ( $undoMsg ===
null ) {
1518 $oldContent = $this->page->getContent( RevisionRecord::RAW );
1519 $parserOptions = ParserOptions::newFromUserAndLang(
1520 $this->getUserForPreview(),
1524 $newContent = $contentTransformer->preSaveTransform(
1525 $content, $this->mTitle, $this->getUserForPreview(), $parserOptions
1528 if ( $newContent->getModel() !== $oldContent->getModel() ) {
1533 $this->contentModel = $newContent->getModel();
1534 $oldMainSlot = $oldrev->
getSlot(
1538 $this->contentFormat = $oldMainSlot->getFormat();
1539 if ( $this->contentFormat ===
null ) {
1540 $this->contentFormat = $this->contentHandlerFactory
1541 ->getContentHandler( $oldMainSlot->getModel() )
1542 ->getDefaultFormat();
1546 if ( $newContent->equals( $oldContent ) ) {
1547 # Tell the user that the undo results in no change,
1548 # i.e. the revisions were already undone.
1549 $undoMsg =
'nochange';
1552 # Inform the user of our success and set an automatic edit summary
1553 $undoMsg =
'success';
1554 $this->generateUndoEditSummary( $oldrev, $undo, $undorev, $services );
1555 $this->undidRev = $undo;
1556 $this->undoAfter = $undoafter;
1557 $this->formtype =
'diff';
1567 $out = $this->context->getOutput();
1570 $class =
"mw-undo-{$undoMsg}";
1571 $html = $this->context->msg(
'undo-' . $undoMsg )->parse();
1572 if ( $undoMsg !==
'success' ) {
1573 $html = Html::errorBox( $html );
1575 $this->editFormPageTop .= Html::rawElement(
1577 [
'class' => $class ],
1582 if ( $content ===
false ) {
1583 $content = $this->getOriginalContent( $this->getAuthority() );
1600 private function generateUndoEditSummary( ?
RevisionRecord $oldrev,
int $undo,
1604 $firstrev = $this->revisionStore->getNextRevision( $oldrev );
1606 if ( $firstrev && $firstrev->getId() == $undo ) {
1607 $userText = $undorev->
getUser() ?
1608 $undorev->
getUser()->getName() :
1610 if ( $userText ===
'' ) {
1611 $undoSummary = $this->context->msg(
1612 'undo-summary-username-hidden',
1614 )->inContentLanguage()->text();
1616 } elseif ( ExternalUserNames::isExternal( $userText ) ) {
1617 $userLinkTitle = ExternalUserNames::getUserLinkTitle( $userText );
1618 if ( $userLinkTitle ) {
1619 $userLink = $userLinkTitle->getPrefixedText();
1620 $undoSummary = $this->context->msg(
1621 'undo-summary-import',
1625 )->inContentLanguage()->text();
1627 $undoSummary = $this->context->msg(
1628 'undo-summary-import2',
1631 )->inContentLanguage()->text();
1636 !$undorev->
getUser()->isRegistered();
1637 $disableAnonTalk = $services->
getMainConfig()->get( MainConfigNames::DisableAnonTalk );
1638 $undoMessage = ( $undoIsAnon && $disableAnonTalk ) ?
1639 'undo-summary-anon' :
1641 $undoSummary = $this->context->msg(
1645 )->inContentLanguage()->text();
1647 if ( $this->summary ===
'' ) {
1648 $this->summary = $undoSummary;
1650 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1651 ->inContentLanguage()->text() . $this->summary;
1655 $firstRevisionId = $firstrev->getId();
1656 $lastRevisionId = $undorev->
getId();
1658 $firstrev->getPageId(),
1662 [ RevisionStore::INCLUDE_BOTH, RevisionStore::INCLUDE_DELETED_REVISIONS ]
1664 $undoSummary = $this->context->msg(
'undo-summary-multiple' )
1665 ->numParams( $revisionCount )
1666 ->params( $firstRevisionId, $lastRevisionId )
1667 ->inContentLanguage()
1669 if ( $this->summary ===
'' ) {
1670 $this->summary = $undoSummary;
1672 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1673 ->inContentLanguage()->text() . $this->summary;
1691 private function getUndoContent( RevisionRecord $undoRev, RevisionRecord $oldRev, &$error ) {
1692 $handler = $this->contentHandlerFactory
1693 ->getContentHandler( $undoRev->getSlot(
1697 $currentContent = $this->page->getRevisionRecord()
1698 ->getContent( SlotRecord::MAIN );
1699 $undoContent = $undoRev->getContent( SlotRecord::MAIN );
1700 $undoAfterContent = $oldRev->getContent( SlotRecord::MAIN );
1701 $undoIsLatest = $this->page->getRevisionRecord()->getId() === $undoRev->getId();
1702 if ( $currentContent ===
null
1703 || $undoContent ===
null
1704 || $undoAfterContent ===
null
1710 $content = $handler->getUndoContent(
1716 if ( $content ===
false ) {
1736 private function getOriginalContent( Authority $performer ): ?Content {
1737 if ( $this->section ===
'new' ) {
1738 return $this->getCurrentContent();
1740 $revRecord = $this->mArticle->fetchRevisionRecord();
1741 if ( $revRecord ===
null ) {
1742 return $this->contentHandlerFactory
1743 ->getContentHandler( $this->contentModel )
1744 ->makeEmptyContent();
1746 return $revRecord->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER, $performer );
1760 private function getParentRevId() {
1761 if ( $this->parentRevId ) {
1762 return $this->parentRevId;
1764 return $this->mArticle->getRevIdFetched();
1777 $revRecord = $this->page->getRevisionRecord();
1778 $content = $revRecord ? $revRecord->getContent(
1783 if ( $content ===
null ) {
1784 return $this->contentHandlerFactory
1785 ->getContentHandler( $this->contentModel )
1786 ->makeEmptyContent();
1798 private function tokenOk(
WebRequest $request ): bool {
1799 $token = $request->getVal(
'wpEditToken' );
1800 $user = $this->context->getUser();
1801 $this->mTokenOk = $user->matchEditToken( $token );
1802 return $this->mTokenOk;
1819 private function setPostEditCookie(
int $statusValue ): void {
1820 $revisionId = $this->page->getLatest();
1821 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1824 if ( $statusValue === self::AS_SUCCESS_NEW_ARTICLE ) {
1826 } elseif ( $this->oldid ) {
1829 if ( $this->tempUserCreateDone ) {
1830 $val .=
'+tempuser';
1833 $response = $this->context->getRequest()->response();
1834 $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1845 $markAsBot = $this->markAsBot
1846 && $this->getAuthority()->isAllowed(
'bot' );
1849 $markAsMinor = $this->minoredit && !$this->isNew
1850 && $this->getAuthority()->isAllowed(
'minoredit' );
1852 $status = $this->internalAttemptSave( $resultDetails, $markAsBot, $markAsMinor );
1854 $this->getHookRunner()->onEditPage__attemptSave_after( $this, $status, $resultDetails );
1862 private function incrementResolvedConflicts(): void {
1863 if ( $this->context->getRequest()->getText(
'mode' ) !==
'conflict' ) {
1867 $this->getEditConflictHelper()->incrementResolvedStats( $this->context->getUser() );
1879 private function handleStatus( Status $status, $resultDetails ): bool {
1880 $statusValue = is_int( $status->value ) ? $status->value : 0;
1886 if ( $statusValue === self::AS_SUCCESS_UPDATE
1887 || $statusValue === self::AS_SUCCESS_NEW_ARTICLE
1889 $this->incrementResolvedConflicts();
1891 $this->didSave =
true;
1892 if ( !$resultDetails[
'nullEdit'] ) {
1893 $this->setPostEditCookie( $statusValue );
1897 $out = $this->context->getOutput();
1901 $request = $this->context->getRequest();
1902 $extraQueryRedirect = $request->
getVal(
'wpExtraQueryRedirect' );
1904 switch ( $statusValue ) {
1907 case self::AS_HOOK_ERROR_EXPECTED:
1908 case self::AS_ARTICLE_WAS_DELETED:
1909 case self::AS_CONFLICT_DETECTED:
1910 case self::AS_SUMMARY_NEEDED:
1912 case self::AS_REVISION_WAS_DELETED:
1915 case self::AS_HOOK_ERROR:
1920 case self::AS_BLANK_ARTICLE:
1921 case self::AS_BROKEN_REDIRECT:
1922 case self::AS_DOUBLE_REDIRECT:
1923 case self::AS_DOUBLE_REDIRECT_LOOP:
1924 case self::AS_CONTENT_TOO_BIG:
1925 case self::AS_INVALID_REDIRECT_TARGET:
1926 case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
1927 case self::AS_PARSE_ERROR:
1928 case self::AS_SELF_REDIRECT:
1929 case self::AS_TEXTBOX_EMPTY:
1930 case self::AS_UNABLE_TO_ACQUIRE_TEMP_ACCOUNT:
1931 case self::AS_UNICODE_NOT_SUPPORTED:
1932 foreach ( $status->getMessages() as $msg ) {
1933 $out->addHTML( Html::errorBox(
1934 $this->context->msg( $msg )->parse()
1939 case self::AS_SUCCESS_NEW_ARTICLE:
1941 if ( $resultDetails[
'redirect'] ) {
1942 $queryParts[] =
'redirect=no';
1944 if ( $extraQueryRedirect ) {
1945 $queryParts[] = $extraQueryRedirect;
1947 $anchor = $resultDetails[
'sectionanchor'] ??
'';
1948 $this->doPostEditRedirect( implode(
'&', $queryParts ), $anchor );
1951 case self::AS_SUCCESS_UPDATE:
1953 $sectionanchor = $resultDetails[
'sectionanchor'];
1955 $this->getHookRunner()->onArticleUpdateBeforeRedirect( $this->mArticle,
1956 $sectionanchor, $extraQuery );
1959 if ( $resultDetails[
'redirect'] ) {
1960 $queryParts[] =
'redirect=no';
1962 if ( $extraQuery ) {
1963 $queryParts[] = $extraQuery;
1965 if ( $extraQueryRedirect ) {
1966 $queryParts[] = $extraQueryRedirect;
1968 $this->doPostEditRedirect( implode(
'&', $queryParts ), $sectionanchor );
1971 case self::AS_SPAM_ERROR:
1972 $this->spamPageWithContent( $resultDetails[
'spam'] ??
false );
1975 case self::AS_BLOCKED_PAGE_FOR_USER:
1976 throw new UserBlockedError(
1978 $this->context->getUser()->getBlock(),
1979 $this->context->getUser(),
1980 $this->context->getLanguage(),
1984 case self::AS_IMAGE_REDIRECT_ANON:
1985 case self::AS_IMAGE_REDIRECT_LOGGED:
1986 throw new PermissionsError(
'upload' );
1988 case self::AS_READ_ONLY_PAGE_ANON:
1989 case self::AS_READ_ONLY_PAGE_LOGGED:
1990 throw new PermissionsError(
'edit' );
1992 case self::AS_READ_ONLY_PAGE:
1993 throw new ReadOnlyError;
1995 case self::AS_RATE_LIMITED:
1996 $out->addHTML( Html::errorBox(
1997 $this->context->msg(
'actionthrottledtext' )->parse()
2001 case self::AS_NO_CREATE_PERMISSION:
2002 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
2003 throw new PermissionsError( $permission );
2005 case self::AS_NO_CHANGE_CONTENT_MODEL:
2006 throw new PermissionsError(
'editcontentmodel' );
2013 $this->hookError = Html::errorBox(
2014 "\n" . $status->getWikiText(
false,
false, $this->context->getLanguage() )
2027 private function doPostEditRedirect( $query, $anchor ) {
2028 $out = $this->context->getOutput();
2029 $url = $this->mTitle->getFullURL( $query ) . $anchor;
2030 $user = $this->getUserForSave();
2039 $shouldRedirectForTempUser = $this->tempUserCreateDone ||
2040 ( $user->isTemp() && ( $user->getEditCount() === 0 ) );
2041 if ( $shouldRedirectForTempUser ) {
2042 $this->getHookRunner()->onTempUserCreatedRedirect(
2043 $this->context->getRequest()->getSession(),
2045 $this->mTitle->getPrefixedDBkey(),
2051 $out->redirect(
$url );
2057 private function setNewSectionSummary(): void {
2058 Assert::precondition( $this->section ===
'new',
'This method can only be called for new sections' );
2059 Assert::precondition( $this->sectiontitle !==
null,
'This method can only be called for new sections' );
2061 $services = MediaWikiServices::getInstance();
2067 if ( $this->sectiontitle !==
'' ) {
2068 $this->newSectionAnchor = $this->guessSectionName( $this->sectiontitle );
2072 if ( $this->summary ===
'' ) {
2073 $messageValue = MessageValue::new(
'newsectionsummary' )
2074 ->plaintextParams( $parser->stripSectionName( $this->sectiontitle ) );
2075 $this->summary = $textFormatter->format( $messageValue );
2078 $this->newSectionAnchor =
'';
2108 private function internalAttemptSave( &$result, $markAsBot =
false, $markAsMinor =
false ) {
2110 if ( $this->unableToAcquireTempName ) {
2111 $status = Status::newFatal(
'temp-user-unable-to-acquire' );
2112 $status->value = self::AS_UNABLE_TO_ACQUIRE_TEMP_ACCOUNT;
2121 $tempAccountStatus = $this->createTempUser();
2122 if ( !$tempAccountStatus->isOK() ) {
2123 return $tempAccountStatus;
2125 if ( $tempAccountStatus instanceof CreateStatus ) {
2126 $result[
'savedTempUser'] = $tempAccountStatus->getUser();
2129 $useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::UseNPPatrol );
2130 $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::UseRCPatrol );
2131 if ( !$this->getHookRunner()->onEditPage__attemptSave( $this ) ) {
2132 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving" );
2133 $status = Status::newFatal(
'hookaborted' );
2134 $status->value = self::AS_HOOK_ERROR;
2138 if ( !$this->getHookRunner()->onEditFilter( $this, $this->textbox1, $this->section,
2139 $this->hookError, $this->summary )
2141 # Error messages etc. could be handled within the hook...
2142 $status = Status::newFatal(
'hookaborted' );
2143 $status->value = self::AS_HOOK_ERROR;
2145 } elseif ( $this->hookError ) {
2146 # ...or the hook could be expecting us to produce an error
2147 $status = Status::newFatal(
'hookaborted' );
2148 $status->value = self::AS_HOOK_ERROR_EXPECTED;
2153 # Construct Content object
2154 $textbox_content = $this->toEditContent( $this->textbox1 );
2155 }
catch ( MWContentSerializationException $ex ) {
2156 $status = Status::newFatal(
2157 'content-failed-to-parse',
2158 $this->contentModel,
2159 $this->contentFormat,
2162 $status->value = self::AS_PARSE_ERROR;
2166 $this->contentLength = strlen( $this->textbox1 );
2168 $requestUser = $this->context->getUser();
2170 $pstUser = $this->getUserForPreview();
2172 $changingContentModel =
false;
2173 if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
2174 $changingContentModel =
true;
2175 $oldContentModel = $this->mTitle->getContentModel();
2180 $constraintFactory = MediaWikiServices::getInstance()->getService(
'_EditConstraintFactory' );
2181 $constraintRunner =
new EditConstraintRunner();
2184 $submitButtonLabel = $this->getSubmitButtonLabel();
2187 $constraintRunner->addConstraint(
2188 new UnicodeConstraint( $this->unicodeCheck )
2194 $constraintRunner->addConstraint(
2195 $constraintFactory->newSimpleAntiSpamConstraint(
2196 $this->context->getRequest()->getText(
'wpAntispam' ),
2203 $constraintRunner->addConstraint(
2204 $constraintFactory->newSpamRegexConstraint(
2206 $this->sectiontitle,
2208 $this->context->getRequest()->getIP(),
2212 $constraintRunner->addConstraint(
2213 new ImageRedirectConstraint(
2219 $constraintRunner->addConstraint(
2220 $constraintFactory->newReadOnlyConstraint()
2225 $this->page->loadPageData( IDBAccessObject::READ_LATEST );
2226 $new = !$this->page->exists();
2228 $constraintRunner->addConstraint(
2229 new AuthorizationConstraint(
2235 $constraintRunner->addConstraint(
2236 new ContentModelChangeConstraint(
2242 $constraintRunner->addConstraint(
2243 $constraintFactory->newLinkPurgeRateLimitConstraint(
2244 $requestUser->toRateLimitSubject()
2247 $constraintRunner->addConstraint(
2250 $constraintFactory->newPageSizeConstraint(
2251 $this->contentLength,
2252 PageSizeConstraint::BEFORE_MERGE
2255 $constraintRunner->addConstraint(
2256 new ChangeTagsConstraint( $authority, $this->changeTags )
2261 $constraintRunner->addConstraint(
2262 new AccidentalRecreationConstraint(
2263 $this->wasDeletedSinceLastEdit(),
2269 if ( !$constraintRunner->checkConstraints() ) {
2270 $failed = $constraintRunner->getFailedConstraint();
2274 if ( $failed instanceof SpamRegexConstraint ) {
2275 $result[
'spam'] = $failed->getMatch();
2277 $this->handleFailedConstraint( $failed );
2280 return Status::wrap( $failed->getLegacyStatus() );
2290 $content = $textbox_content;
2292 $result[
'sectionanchor'] =
'';
2293 if ( $this->section ===
'new' ) {
2294 if ( $this->sectiontitle !==
null ) {
2296 $content = $content->addSectionHeader( $this->sectiontitle );
2298 $result[
'sectionanchor'] = $this->newSectionAnchor;
2301 $pageUpdater = $this->page->newPageUpdater( $pstUser )
2303 ->setContent( SlotRecord::MAIN, $content );
2304 $pageUpdater->prepareUpdate( $flags );
2308 $constraintRunner =
new EditConstraintRunner();
2313 $constraintRunner->addConstraint(
2314 new DefaultTextConstraint(
2316 $this->allowBlankArticle,
2322 $constraintRunner->addConstraint(
2323 $constraintFactory->newEditFilterMergedContentHookConstraint(
2328 $this->context->getLanguage(),
2334 if ( !$constraintRunner->checkConstraints() ) {
2335 $failed = $constraintRunner->getFailedConstraint();
2336 $this->handleFailedConstraint( $failed );
2337 return Status::wrap( $failed->getLegacyStatus() );
2342 # Article exists. Check for edit conflict.
2344 $timestamp = $this->page->getTimestamp();
2345 $latest = $this->page->getLatest();
2347 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}" );
2348 wfDebug(
"revision: {$latest}, editRevId: {$this->editRevId}" );
2350 $editConflictLogger = LoggerFactory::getInstance(
'EditConflict' );
2355 if ( ( $this->edittime !==
null && $this->edittime != $timestamp )
2356 || ( $this->editRevId !==
null && $this->editRevId != $latest )
2358 $this->isConflict =
true;
2359 if ( $this->section ===
'new' ) {
2360 if ( $this->page->getUserText() === $requestUser->getName() &&
2361 $this->page->getComment() === $this->summary
2366 $editConflictLogger->debug(
2367 'Duplicate new section submission; trigger edit conflict!'
2371 $this->isConflict =
false;
2372 $editConflictLogger->debug(
'Conflict suppressed; new section' );
2374 } elseif ( $this->section ===
''
2376 && $this->revisionStore->userWasLastToEdit(
2377 $this->dbProvider->getPrimaryDatabase(),
2378 $this->mTitle->getArticleID(),
2379 $requestUser->getId(),
2383 # Suppress edit conflict with self, except for section edits where merging is required.
2384 $editConflictLogger->debug(
'Suppressing edit conflict, same user.' );
2385 $this->isConflict =
false;
2389 if ( $this->isConflict ) {
2390 $editConflictLogger->debug(
2391 'Conflict! Getting section {section} for time {editTime}'
2392 .
' (id {editRevId}, article time {timestamp})',
2394 'section' => $this->section,
2395 'editTime' => $this->edittime,
2396 'editRevId' => $this->editRevId,
2397 'timestamp' => $timestamp,
2402 if ( $this->editRevId !==
null ) {
2403 $content = $this->page->replaceSectionAtRev(
2406 $this->sectiontitle,
2410 $content = $this->page->replaceSectionContent(
2413 $this->sectiontitle,
2418 $editConflictLogger->debug(
2419 'Getting section {section}',
2420 [
'section' => $this->section ]
2422 $content = $this->page->replaceSectionAtRev(
2429 if ( $content ===
null ) {
2430 $editConflictLogger->debug(
'Activating conflict; section replace failed.' );
2431 $this->isConflict =
true;
2432 $content = $textbox_content;
2433 } elseif ( $this->isConflict ) {
2435 $mergedChange = $this->mergeChangesIntoContent( $content );
2436 if ( $mergedChange !==
false ) {
2438 $content = $mergedChange[0];
2439 $this->parentRevId = $mergedChange[1];
2440 $this->isConflict =
false;
2441 $editConflictLogger->debug(
'Suppressing edit conflict, successful merge.' );
2443 $this->section =
'';
2444 $this->textbox1 = ( $content instanceof TextContent ) ? $content->getText() :
'';
2445 $editConflictLogger->debug(
'Keeping edit conflict, failed merge.' );
2449 if ( $this->isConflict ) {
2450 return Status::newGood( self::AS_CONFLICT_DETECTED )->setOK(
false );
2453 $pageUpdater = $this->page->newPageUpdater( $pstUser )
2454 ->setContent( SlotRecord::MAIN, $content );
2455 $pageUpdater->prepareUpdate( $flags );
2459 $constraintRunner =
new EditConstraintRunner();
2460 $constraintRunner->addConstraint(
2461 $constraintFactory->newEditFilterMergedContentHookConstraint(
2466 $this->context->getLanguage(),
2470 $constraintRunner->addConstraint(
2471 new NewSectionMissingSubjectConstraint(
2473 $this->sectiontitle ??
'',
2474 $this->allowBlankSummary
2477 $constraintRunner->addConstraint(
2478 new MissingCommentConstraint( $this->section, $this->textbox1 )
2480 $constraintRunner->addConstraint(
2481 new ExistingSectionEditConstraint(
2485 $this->allowBlankSummary,
2487 $this->getOriginalContent( $authority )
2491 if ( !$constraintRunner->checkConstraints() ) {
2492 $failed = $constraintRunner->getFailedConstraint();
2493 $this->handleFailedConstraint( $failed );
2494 return Status::wrap( $failed->getLegacyStatus() );
2499 $sectionAnchor =
'';
2500 if ( $this->section ===
'new' ) {
2501 $sectionAnchor = $this->newSectionAnchor;
2502 } elseif ( $this->section !==
'' ) {
2503 # Try to get a section anchor from the section source, redirect
2504 # to edited section if header found.
2505 # XXX: Might be better to integrate this into WikiPage::replaceSectionAtRev
2506 # for duplicate heading checking and maybe parsing.
2507 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
2508 # We can't deal with anchors, includes, html etc in the header for now,
2509 # headline would need to be parsed to improve this.
2510 if ( $hasmatch &&
$matches[2] !==
'' ) {
2511 $sectionAnchor = $this->guessSectionName(
$matches[2] );
2514 $result[
'sectionanchor'] = $sectionAnchor;
2520 $this->textbox1 = $this->toEditText( $content );
2521 $this->section =
'';
2525 $this->contentLength = strlen( $this->toEditText( $content ) );
2529 $constraintRunner =
new EditConstraintRunner();
2530 if ( !$this->ignoreProblematicRedirects ) {
2531 $constraintRunner->addConstraint(
2532 new RedirectConstraint(
2533 $this->allowedProblematicRedirectTarget,
2535 $this->getCurrentContent(),
2538 $this->contentFormat,
2539 $this->redirectLookup
2543 $constraintRunner->addConstraint(
2546 $constraintFactory->newPageSizeConstraint(
2547 $this->contentLength,
2548 PageSizeConstraint::AFTER_MERGE
2552 if ( !$constraintRunner->checkConstraints() ) {
2553 $failed = $constraintRunner->getFailedConstraint();
2554 $this->handleFailedConstraint( $failed );
2555 return Status::wrap( $failed->getLegacyStatus() );
2559 if ( $this->undidRev && $this->isUndoClean( $content ) ) {
2564 ->setOriginalRevisionId( $this->undoAfter ?: false )
2565 ->setCause( PageUpdateCauses::CAUSE_UNDO )
2567 EditResult::REVERT_UNDO,
2569 $this->undoAfter ?: null
2573 $needsPatrol = $useRCPatrol || ( $useNPPatrol && !$this->page->exists() );
2574 if ( $needsPatrol && $authority->authorizeWrite(
'autopatrol', $this->getTitle() ) ) {
2575 $pageUpdater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
2579 ->addTags( $this->changeTags )
2581 CommentStoreComment::newUnsavedComment( trim( $this->summary ) ),
2584 $doEditStatus = $pageUpdater->getStatus();
2586 if ( !$doEditStatus->isOK() ) {
2591 $doEditStatus->failedBecausePageMissing() ||
2592 $doEditStatus->failedBecausePageExists() ||
2593 $doEditStatus->failedBecauseOfConflict()
2595 $this->isConflict =
true;
2599 $doEditStatus->value = self::AS_END;
2601 return $doEditStatus;
2604 $result[
'nullEdit'] = !$doEditStatus->wasRevisionCreated();
2605 if ( $result[
'nullEdit'] ) {
2607 $limitSubject = $requestUser->toRateLimitSubject();
2608 MediaWikiServices::getInstance()->getRateLimiter()->limit( $limitSubject,
'linkpurge' );
2610 $result[
'redirect'] = $content->isRedirect();
2612 $this->updateWatchlist();
2615 if ( $changingContentModel ) {
2616 $this->addContentModelChangeLogEntry(
2617 $this->getUserForSave(),
2620 $new ?
false : $oldContentModel,
2621 $this->contentModel,
2629 $statusCode = ( $new ? self::AS_SUCCESS_NEW_ARTICLE : self::AS_SUCCESS_UPDATE );
2630 return Status::newGood( $statusCode );
2639 private function handleFailedConstraint( IEditConstraint $failed ): void {
2640 if ( $failed instanceof AuthorizationConstraint ) {
2643 !MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly()
2644 && $failed->getLegacyStatus()->value === self::AS_BLOCKED_PAGE_FOR_USER
2646 $this->context->getUser()->spreadAnyEditBlock();
2648 } elseif ( $failed instanceof DefaultTextConstraint ) {
2649 $this->blankArticle =
true;
2650 } elseif ( $failed instanceof EditFilterMergedContentHookConstraint ) {
2651 $this->hookError = $failed->getHookError();
2656 $failed instanceof ExistingSectionEditConstraint
2657 && $failed->getLegacyStatus()->value === self::AS_SUMMARY_NEEDED
2659 $failed instanceof NewSectionMissingSubjectConstraint
2661 $this->missingSummary =
true;
2662 } elseif ( $failed instanceof RedirectConstraint ) {
2663 $this->problematicRedirectTarget = $failed->problematicTarget;
2677 private function isUndoClean( Content $content ): bool {
2680 $undoRev = $this->revisionStore->getRevisionById( $this->undidRev );
2681 if ( $undoRev ===
null ) {
2685 if ( $this->undoAfter ) {
2686 $oldRev = $this->revisionStore->getRevisionById( $this->undoAfter );
2688 $oldRev = $this->revisionStore->getPreviousRevision( $undoRev );
2691 if ( $oldRev ===
null ||
2692 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
2693 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
2698 $undoContent = $this->getUndoContent( $undoRev, $oldRev, $undoError );
2699 if ( !$undoContent ) {
2704 $services = MediaWikiServices::getInstance();
2706 $user = $this->getUserForPreview();
2707 $parserOptions = ParserOptions::newFromUserAndLang( $user, $contentLanguage );
2709 $undoContent = $contentTransformer->preSaveTransform( $undoContent, $this->mTitle, $user, $parserOptions );
2711 if ( $undoContent->equals( $content ) ) {
2723 private function addContentModelChangeLogEntry( UserIdentity $user, $oldModel, $newModel, $reason =
"" ): void {
2724 $new = $oldModel === false;
2725 $log =
new ManualLogEntry(
'contentmodel', $new ?
'new' :
'change' );
2726 $log->setPerformer( $user );
2727 $log->setTarget( $this->mTitle );
2728 $log->setComment( is_string( $reason ) ? $reason :
"" );
2729 $log->setParameters( [
2730 '4::oldmodel' => $oldModel,
2731 '5::newmodel' => $newModel
2733 $logid = $log->insert();
2734 $log->publish( $logid );
2740 private function updateWatchlist(): void {
2741 if ( $this->tempUserCreateActive ) {
2744 $user = $this->getUserForSave();
2745 if ( !$user->isNamed() ) {
2749 $title = $this->mTitle;
2750 $watch = $this->watchthis;
2751 $watchlistExpiry = $this->watchlistExpiry;
2756 $this->watchlistManager->setWatch( $watch, $user, $title, $watchlistExpiry );
2758 $this->watchedItemStore->maybeEnqueueWatchlistExpiryJob();
2771 private function mergeChangesIntoContent( Content $editContent ) {
2774 $baseRevRecord = $this->getExpectedParentRevision();
2775 $baseContent = $baseRevRecord ?
2776 $baseRevRecord->getContent( SlotRecord::MAIN ) :
2779 if ( $baseContent ===
null ) {
2781 } elseif ( $baseRevRecord->isCurrent() ) {
2784 return [ $editContent, $baseRevRecord->getId() ];
2788 $currentRevisionRecord = $this->revisionStore->getRevisionByTitle(
2791 IDBAccessObject::READ_LATEST
2793 $currentContent = $currentRevisionRecord
2794 ? $currentRevisionRecord->getContent( SlotRecord::MAIN )
2797 if ( $currentContent ===
null ) {
2801 $mergedContent = $this->contentHandlerFactory
2802 ->getContentHandler( $baseContent->getModel() )
2803 ->merge3( $baseContent, $editContent, $currentContent );
2805 if ( $mergedContent ) {
2807 return [ $mergedContent, $currentRevisionRecord->getId() ];
2821 if ( $this->mExpectedParentRevision ===
false ) {
2823 if ( $this->editRevId ) {
2824 $revRecord = $this->revisionStore->getRevisionById(
2826 IDBAccessObject::READ_LATEST
2828 } elseif ( $this->edittime ) {
2829 $revRecord = $this->revisionStore->getRevisionByTimestamp(
2832 IDBAccessObject::READ_LATEST
2835 $this->mExpectedParentRevision = $revRecord;
2837 return $this->mExpectedParentRevision;
2841 $out = $this->context->getOutput();
2843 $out->addModules(
'mediawiki.action.edit' );
2844 $out->addModuleStyles( [
2845 'mediawiki.action.edit.styles',
2846 'mediawiki.codex.messagebox.styles',
2847 'mediawiki.editfont.styles',
2848 'mediawiki.interface.helpers.styles',
2851 $user = $this->context->getUser();
2853 if ( $this->userOptionsLookup->getOption( $user,
'uselivepreview' ) ) {
2854 $out->addModules(
'mediawiki.action.edit.preview' );
2857 if ( $this->userOptionsLookup->getOption( $user,
'useeditwarning' ) ) {
2858 $out->addModules(
'mediawiki.action.edit.editWarning' );
2861 if ( $this->context->getConfig()->get( MainConfigNames::EnableEditRecovery )
2862 && $this->userOptionsLookup->getOption( $user,
'editrecovery' )
2864 $wasPosted = $this->getContext()->getRequest()->getMethod() ===
'POST';
2865 $out->addJsConfigVars(
'wgEditRecoveryWasPosted', $wasPosted );
2866 $out->addModules(
'mediawiki.editRecovery.edit' );
2869 # Enabled article-related sidebar, toplinks, etc.
2870 $out->setArticleRelated(
true );
2872 $contextTitle = $this->getContextTitle();
2873 if ( $this->isConflict ) {
2874 $msg =
'editconflict';
2875 } elseif ( $contextTitle->exists() && $this->section !=
'' ) {
2876 $msg = $this->section ===
'new' ?
'editingcomment' :
'editingsection';
2878 $msg = $contextTitle->exists()
2880 && $contextTitle->getDefaultMessageText() !== false
2886 # Use the title defined by DISPLAYTITLE magic word when present
2887 # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2888 # Escape ::getPrefixedText() so that we have HTML in all cases,
2889 # and pass as a "raw" parameter to ::setPageTitleMsg().
2890 $displayTitle = $this->mParserOutput ? $this->mParserOutput->getDisplayTitle() :
false;
2891 if ( $displayTitle ===
false ) {
2892 $displayTitle = htmlspecialchars(
2893 $contextTitle->getPrefixedText(), ENT_QUOTES,
'UTF-8',
false
2896 $out->setDisplayTitle( $displayTitle );
2901 $displayTitle = Html::rawElement(
'span', [
'id' =>
'firstHeadingTitle' ], $displayTitle );
2903 $out->setPageTitleMsg( $this->context->msg( $msg )->rawParams( $displayTitle ) );
2905 $config = $this->context->getConfig();
2907 # Transmit the name of the message to JavaScript. This was added for live preview.
2908 # Live preview doesn't use this anymore. The variable is still transmitted because
2909 # Edit Recovery and user scripts use it.
2910 $out->addJsConfigVars( [
2911 'wgEditMessage' => $msg,
2916 $out->addJsConfigVars(
2917 'wgEditSubmitButtonLabelPublish',
2918 $config->get( MainConfigNames::EditSubmitButtonLabelPublish )
2925 private function showIntro(): void {
2934 $skip = $this->suppressIntro ? [
2936 'code-editing-intro',
2937 'sharedupload-desc-create',
2938 'sharedupload-desc-edit',
2939 'userpage-userdoesnotexist',
2940 'blocked-notice-logextract',
2942 'newarticletextanon',
2943 'recreate-moveddeleted-warn',
2947 IntroMessageBuilder::MORE_FRAMES,
2950 $this->mTitle->toPageIdentity(),
2951 $this->mArticle->fetchRevisionRecord(),
2952 $this->context->getUser(),
2953 $this->context->getRequest()->getVal(
'editintro' ),
2956 $this->context->getRequest()->getQueryValues(),
2957 [
'title' =>
true,
'returnto' =>
true,
'returntoquery' =>
true ]
2961 $this->section !==
'' ? $this->section : null
2964 foreach ( $messages as $message ) {
2965 $this->context->getOutput()->addHTML( $message );
2987 private function toEditText( $content ) {
2988 if ( $content ===
null || $content ===
false ) {
2991 if ( is_string( $content ) ) {
2995 if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2996 throw new MWException(
'This content model is not supported: ' . $content->getModel() );
2999 return $content->serialize( $this->contentFormat );
3019 if ( $text ===
false || $text ===
null ) {
3023 $content = ContentHandler::makeContent( $text, $this->getTitle(),
3024 $this->contentModel, $this->contentFormat );
3026 if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
3027 throw new MWException(
'This content model is not supported: ' . $content->getModel() );
3037 # need to parse the preview early so that we know which templates are used,
3038 # otherwise users with "show preview after edit box" will get a blank list
3039 # we parse this near the beginning so that setHeaders can do the title
3040 # setting work instead of leaving it in getPreviewText
3041 $previewOutput =
'';
3042 if ( $this->formtype ===
'preview' ) {
3043 $previewOutput = $this->getPreviewText();
3046 $out = $this->context->getOutput();
3049 $this->getHookRunner()->onEditPage__showEditForm_initial( $this, $out );
3051 $this->setHeaders();
3056 if ( !$this->isConflict &&
3057 $this->section !==
'' &&
3058 !$this->isSectionEditSupported()
3063 $out->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
3067 $this->showHeader();
3069 $out->addHTML( $this->editFormPageTop );
3071 $user = $this->context->getUser();
3072 if ( $this->userOptionsLookup->getOption( $user,
'previewontop' ) ) {
3073 $this->displayPreviewArea( $previewOutput,
true );
3076 $out->addHTML( $this->editFormTextTop );
3078 if ( $this->formtype !==
'save' && $this->wasDeletedSinceLastEdit() ) {
3079 $out->addHTML( Html::errorBox(
3080 $out->msg(
'deletedwhileediting' )->parse(),
3082 'mw-deleted-while-editing'
3088 $out->addHTML( Html::openElement(
3091 'class' =>
'mw-editform',
3092 'id' => self::EDITFORM_ID,
3093 'name' => self::EDITFORM_ID,
3095 'action' => $this->getActionURL( $this->getContextTitle() ),
3096 'enctype' =>
'multipart/form-data',
3097 'data-mw-editform-type' => $this->formtype
3102 $out->addHTML( Html::hidden(
'wpUnicodeCheck', self::UNICODE_CHECK ) );
3106 Html::openElement(
'div', [
'id' =>
'antispam-container',
'style' =>
'display: none;' ] )
3109 [
'for' =>
'wpAntispam' ],
3110 $this->context->msg(
'simpleantispam-label' )->parse()
3116 'name' =>
'wpAntispam',
3117 'id' =>
'wpAntispam',
3121 . Html::closeElement(
'div' )
3124 $this->getHookRunner()->onEditPage__showEditForm_fields( $this, $out );
3127 $this->showFormBeforeText();
3129 if ( $this->formtype ===
'save' && $this->wasDeletedSinceLastEdit() ) {
3130 $username = $this->lastDelete->actor_name;
3131 $comment = $this->commentStore->getComment(
'log_comment', $this->lastDelete )->text;
3135 $key = $comment ===
''
3136 ?
'confirmrecreate-noreason'
3137 :
'confirmrecreate';
3138 $out->addHTML( Html::rawElement(
3140 [
'class' =>
'mw-confirm-recreate' ],
3141 $this->context->msg( $key )
3142 ->params( $username )
3143 ->plaintextParams( $comment )
3151 [
'title' => Linker::titleAttrib(
'recreate' ),
'tabindex' => 1,
'id' =>
'wpRecreate' ]
3155 $this->context->msg(
'recreate' )->text(),
3157 [
'title' => Linker::titleAttrib(
'recreate' ) ]
3163 # When the summary is hidden, also hide them on preview/show changes
3164 if ( $this->nosummary ) {
3165 $out->addHTML( Html::hidden(
'nosummary',
true ) );
3168 # If a blank edit summary was previously provided, and the appropriate
3169 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
3170 # user being bounced back more than once in the event that a summary
3173 # For a bit more sophisticated detection of blank summaries, hash the
3174 # automatic one and pass that in the hidden field wpAutoSummary.
3176 $this->missingSummary ||
3178 ( $this->section ===
'new' && $this->nosummary ) ||
3179 $this->allowBlankSummary
3181 $out->addHTML( Html::hidden(
'wpIgnoreBlankSummary',
true ) );
3184 if ( $this->undidRev ) {
3185 $out->addHTML( Html::hidden(
'wpUndidRevision', $this->undidRev ) );
3187 if ( $this->undoAfter ) {
3188 $out->addHTML( Html::hidden(
'wpUndoAfter', $this->undoAfter ) );
3191 if ( $this->problematicRedirectTarget !==
null ) {
3194 $out->addHTML( Html::hidden(
3195 'wpAllowedProblematicRedirectTarget',
3196 $this->problematicRedirectTarget->getFullText()
3200 $autosumm = $this->autoSumm !==
'' ? $this->autoSumm : md5( $this->summary );
3201 $out->addHTML( Html::hidden(
'wpAutoSummary', $autosumm ) );
3203 $out->addHTML( Html::hidden(
'oldid', $this->oldid ) );
3204 $out->addHTML( Html::hidden(
'parentRevId', $this->getParentRevId() ) );
3206 $out->addHTML( Html::hidden(
'format', $this->contentFormat ) );
3207 $out->addHTML( Html::hidden(
'model', $this->contentModel ) );
3208 if ( $this->changeTags ) {
3209 $out->addHTML( Html::hidden(
'wpChangeTagsAfterPreview', implode(
',', $this->changeTags ) ) );
3214 if ( $this->section ===
'new' ) {
3215 $this->showSummaryInput(
true );
3216 $out->addHTML( $this->getSummaryPreview(
true ) );
3219 $out->addHTML( $this->editFormTextBeforeContent );
3220 if ( $this->isConflict ) {
3221 $currentText = $this->toEditText( $this->getCurrentContent() );
3223 $editConflictHelper = $this->getEditConflictHelper();
3224 $editConflictHelper->
setTextboxes( $this->textbox1, $currentText );
3229 $this->textbox2 = $this->textbox1;
3230 $this->textbox1 = $currentText;
3233 if ( !$this->mTitle->isUserConfigPage() ) {
3234 $out->addHTML( self::getEditToolbar() );
3237 if ( $this->blankArticle ) {
3238 $out->addHTML( Html::hidden(
'wpIgnoreBlankArticle',
true ) );
3241 if ( $this->isConflict ) {
3246 $conflictTextBoxAttribs = [];
3247 if ( $this->wasDeletedSinceLastEdit() ) {
3248 $conflictTextBoxAttribs[
'style'] =
'display:none;';
3249 } elseif ( $this->isOldRev ) {
3250 $conflictTextBoxAttribs[
'class'] =
'mw-textarea-oldrev';
3260 $this->showContentForm();
3263 $out->addHTML( $this->editFormTextAfterContent );
3265 $this->showStandardInputs();
3267 $this->showFormAfterText();
3269 $this->showTosSummary();
3271 $this->showEditTools();
3273 $out->addHTML( $this->editFormTextAfterTools .
"\n" );
3275 $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
3277 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'hiddencats' ],
3278 Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
3280 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'limitreport' ],
3281 self::getPreviewLimitReport( $this->mParserOutput ) ) );
3283 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
3285 if ( $this->isConflict ) {
3287 $this->showConflict();
3290 $out->addHTML( Html::errorBox(
3291 $this->context->msg(
3292 'content-failed-to-parse',
3293 $this->contentModel,
3294 $this->contentFormat,
3302 if ( $this->isConflict ) {
3304 } elseif ( $this->preview ) {
3306 } elseif ( $this->diff ) {
3311 $out->addHTML( Html::hidden(
'mode', $mode, [
'id' =>
'mw-edit-mode' ] ) );
3315 $out->addHTML( Html::hidden(
'wpUltimateParam',
true ) );
3316 $out->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
3318 if ( !$this->userOptionsLookup->getOption( $user,
'previewontop' ) ) {
3319 $this->displayPreviewArea( $previewOutput,
false );
3333 $this->linkRenderer,
3334 $this->linkBatchFactory,
3335 $this->restrictionStore
3340 if ( $this->preview ) {
3342 } elseif ( $this->section !==
'' ) {
3346 return Html::rawElement(
'div', [
'class' =>
'templatesUsed' ],
3347 $templateListFormatter->format( $templates, $type )
3357 private static function extractSectionTitle( $text ) {
3358 if ( preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches ) ) {
3359 return MediaWikiServices::getInstance()->getParser()
3360 ->stripSectionName( trim(
$matches[2] ) );
3366 private function showHeader(): void {
3368 $user = $this->context->getUser();
3369 if ( $this->isConflict ) {
3370 $this->addExplainConflictHeader();
3371 $this->editRevId = $this->page->getLatest();
3373 if ( $this->section !==
'' && $this->section !==
'new' && $this->summary ===
'' &&
3374 !$this->preview && !$this->diff
3376 $sectionTitle = self::extractSectionTitle( $this->textbox1 );
3377 if ( $sectionTitle !==
false ) {
3378 $this->summary =
"/* $sectionTitle */ ";
3382 $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
3384 if ( $this->missingSummary && $this->section !==
'new' ) {
3386 "<div id='mw-missingsummary'>\n$1\n</div>",
3387 [
'missingsummary', $buttonLabel ]
3391 if ( $this->missingSummary && $this->section ===
'new' ) {
3393 "<div id='mw-missingcommentheader'>\n$1\n</div>",
3394 [
'missingcommentheader', $buttonLabel ]
3398 if ( $this->hookError !==
'' ) {
3399 $out->addWikiTextAsInterface( $this->hookError );
3402 if ( $this->section !=
'new' ) {
3403 $revRecord = $this->mArticle->fetchRevisionRecord();
3404 if ( $revRecord && $revRecord instanceof RevisionStoreRecord ) {
3407 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
3410 $out->msg(
'rev-deleted-text-permission', $this->mTitle->getPrefixedURL() )->parse(),
3414 } elseif ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
3418 $out->msg(
'rev-deleted-text-view', $this->mTitle->getPrefixedURL() )->parse(),
3424 if ( !$revRecord->isCurrent() ) {
3425 $this->mArticle->setOldSubtitle( $revRecord->getId() );
3426 $this->isOldRev =
true;
3428 } elseif ( $this->mTitle->exists() ) {
3433 $out->msg(
'missing-revision', $this->oldid )->parse()
3440 $this->addLongPageWarningHeader();
3450 private function getSummaryInputAttributes( array $inputAttrs ): array {
3455 'id' =>
'wpSummary',
3456 'name' =>
'wpSummary',
3457 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
3460 'spellcheck' =>
'true',
3473 private function getSummaryInputWidget( $summary,
string $labelText, array $inputAttrs ): FieldLayout {
3474 $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3475 $this->getSummaryInputAttributes( $inputAttrs )
3478 'title' => Linker::titleAttrib(
'summary' ),
3479 'accessKey' => Linker::accesskey(
'summary' ),
3483 $inputAttrs[
'inputId'] = $inputAttrs[
'id'];
3484 $inputAttrs[
'id'] =
'wpSummaryWidget';
3486 return new OOUI\FieldLayout(
3487 new OOUI\TextInputWidget( [
3488 'value' => $summary,
3489 'infusable' =>
true,
3492 'label' =>
new OOUI\HtmlSnippet( $labelText ),
3494 'id' =>
'wpSummaryLabel',
3495 'classes' => [ $this->missingSummary ?
'mw-summarymissed' :
'mw-summary' ],
3505 private function showSummaryInput(
bool $isSubjectPreview ): void {
3506 # Add a class
if 'missingsummary' is triggered to allow styling of the summary line
3507 $summaryClass = $this->missingSummary ?
'mw-summarymissed' :
'mw-summary';
3508 if ( $isSubjectPreview ) {
3509 if ( $this->nosummary ) {
3512 } elseif ( !$this->mShowSummaryField ) {
3516 $labelText = $this->context->msg( $isSubjectPreview ?
'subject' :
'summary' )->parse();
3517 $this->context->getOutput()->addHTML(
3518 $this->getSummaryInputWidget(
3519 $isSubjectPreview ? $this->sectiontitle : $this->summary,
3521 [
'class' => $summaryClass ]
3532 private function getSummaryPreview(
bool $isSubjectPreview ): string {
3534 $summary = trim( $this->summary );
3535 if ( $summary ===
'' || ( !$this->preview && !$this->diff ) ) {
3539 $commentFormatter = MediaWikiServices::getInstance()->getCommentFormatter();
3540 $summary = $this->context->msg(
'summary-preview' )->parse()
3541 . $commentFormatter->formatBlock( $summary, $this->mTitle, $isSubjectPreview );
3542 return Html::rawElement(
'div', [
'class' =>
'mw-summary-preview' ], $summary );
3545 private function showFormBeforeText(): void {
3547 $out->addHTML( Html::hidden(
'wpSection', $this->section ) );
3548 $out->addHTML( Html::hidden(
'wpStarttime', $this->starttime ) );
3549 $out->addHTML( Html::hidden(
'wpEdittime', $this->edittime ) );
3550 $out->addHTML( Html::hidden(
'editRevId', $this->editRevId ) );
3551 $out->addHTML( Html::hidden(
'wpScrolltop', $this->scrolltop, [
'id' =>
'wpScrolltop' ] ) );
3567 $this->context->getOutput()->addHTML(
3569 Html::hidden(
"wpEditToken", $this->context->getUser()->getEditToken() ) .
3583 $this->showTextbox1();
3586 private function showTextbox1(): void {
3587 if ( $this->formtype ===
'save' && $this->wasDeletedSinceLastEdit() ) {
3588 $attribs = [
'style' =>
'display:none;' ];
3590 $builder =
new TextboxBuilder();
3591 $classes = $builder->getTextboxProtectionCSSClasses( $this->getTitle() );
3593 # Is an old revision being edited?
3594 if ( $this->isOldRev ) {
3595 $classes[] =
'mw-textarea-oldrev';
3599 'aria-label' => $this->context->msg(
'edit-textarea-aria-label' )->text(),
3601 'class' => $classes,
3612 protected function showTextbox(
string $text,
string $name, array $customAttribs = [] ) {
3614 $attribs = $builder->buildTextboxAttribs(
3617 $this->context->getUser(),
3621 $this->context->getOutput()->addHTML(
3622 Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
3626 private function displayPreviewArea(
string $previewOutput,
bool $isOnTop ): void {
3627 $attribs = [
'id' =>
'wikiPreview' ];
3629 $attribs[
'class'] =
'ontop';
3631 if ( $this->formtype !==
'preview' ) {
3632 $attribs[
'style'] =
'display: none;';
3635 $out = $this->context->getOutput();
3636 $out->addHTML( Html::openElement(
'div', $attribs ) );
3638 if ( $this->formtype ===
'preview' ) {
3639 $this->showPreview( $previewOutput );
3642 $out->addHTML(
'</div>' );
3644 if ( $this->formtype ===
'diff' ) {
3647 }
catch ( MWContentSerializationException $ex ) {
3648 $out->addHTML( Html::errorBox(
3649 $this->context->msg(
3650 'content-failed-to-parse',
3651 $this->contentModel,
3652 $this->contentFormat,
3666 private function showPreview(
string $text ): void {
3667 if ( $this->mArticle instanceof CategoryPage ) {
3668 $this->mArticle->openShowCategory();
3670 # This hook seems slightly odd here, but makes things more
3671 # consistent for extensions.
3672 $out = $this->context->getOutput();
3673 $this->getHookRunner()->onOutputPageBeforeHTML( $out, $text );
3674 $out->addHTML( $text );
3675 if ( $this->mArticle instanceof CategoryPage ) {
3676 $this->mArticle->closeShowCategory();
3688 $oldtitlemsg =
'currentrev';
3689 # if message does not exist, show diff against the preloaded default
3690 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3691 $oldtext = $this->mTitle->getDefaultMessageText();
3692 if ( $oldtext !==
false ) {
3693 $oldtitlemsg =
'defaultmessagetext';
3694 $oldContent = $this->toEditContent( $oldtext );
3699 $oldContent = $this->getCurrentContent();
3702 $textboxContent = $this->toEditContent( $this->textbox1 );
3703 if ( $this->editRevId !==
null ) {
3704 $newContent = $this->page->replaceSectionAtRev(
3705 $this->section, $textboxContent, $this->sectiontitle, $this->editRevId
3708 $newContent = $this->page->replaceSectionContent(
3709 $this->section, $textboxContent, $this->sectiontitle, $this->edittime
3713 if ( $newContent ) {
3714 $this->getHookRunner()->onEditPageGetDiffContent( $this, $newContent );
3716 $user = $this->getUserForPreview();
3717 $parserOptions = ParserOptions::newFromUserAndLang( $user,
3718 MediaWikiServices::getInstance()->getContentLanguage() );
3719 $services = MediaWikiServices::getInstance();
3721 $newContent = $contentTransformer->preSaveTransform( $newContent, $this->mTitle, $user, $parserOptions );
3724 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3725 $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3726 $newtitle = $this->context->msg(
'yourtext' )->parse();
3728 if ( !$oldContent ) {
3729 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3732 if ( !$newContent ) {
3733 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3736 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3737 $de->setContent( $oldContent, $newContent );
3739 $difftext = $de->getDiff( $oldtitle, $newtitle );
3740 $de->showDiffStyle();
3745 $this->context->getOutput()->addHTML( Html::rawElement(
'div', [
'id' =>
'wikiDiff' ], $difftext ) );
3756 private function showTosSummary(): void {
3757 $msgKey =
'editpage-tos-summary';
3758 $this->getHookRunner()->onEditPageTosSummary( $this->mTitle, $msgKey );
3759 $msg = $this->context->msg( $msgKey );
3760 if ( !$msg->isDisabled() ) {
3761 $this->context->getOutput()->addHTML( Html::rawElement(
3763 [
'class' =>
'mw-tos-summary' ],
3764 $msg->parseAsBlock()
3773 private function showEditTools(): void {
3774 $this->context->getOutput()->addHTML( Html::rawElement(
3776 [
'class' =>
'mw-editTools' ],
3777 $this->context->msg(
'edittools' )->inContentLanguage()->parse()
3791 $services = MediaWikiServices::getInstance();
3792 $rightsText = $services->
getMainConfig()->get( MainConfigNames::RightsText );
3793 if ( $rightsText ) {
3794 $copywarnMsg = [
'copyrightwarning',
3795 '[[' . $localizer->
msg(
'copyrightpage' )->inContentLanguage()->text() .
']]',
3798 $copywarnMsg = [
'copyrightwarning2',
3799 '[[' . $localizer->
msg(
'copyrightpage' )->inContentLanguage()->text() .
']]' ];
3802 $title = Title::newFromPageReference( $page );
3804 if ( !$copywarnMsg ) {
3808 $msg = $localizer->
msg( ...$copywarnMsg )->page( $page );
3809 return Html::rawElement(
'div', [
'id' =>
'editpage-copywarn' ], $msg->$format() );
3820 if ( !$output || !$output->getLimitReportData() ) {
3824 $limitReport = Html::rawElement(
'div', [
'class' =>
'mw-limitReportExplanation' ],
3825 wfMessage(
'limitreport-title' )->parseAsBlock()
3829 $limitReport .= Html::openElement(
'div', [
'class' =>
'preview-limit-report-wrapper' ] );
3831 $limitReport .= Html::openElement(
'table', [
3832 'class' =>
'preview-limit-report wikitable'
3834 Html::openElement(
'tbody' );
3836 $hookRunner =
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
3837 foreach ( $output->getLimitReportData() as $key => $value ) {
3838 if ( in_array( $key, [
3839 'cachereport-origin',
3840 'cachereport-timestamp',
3842 'cachereport-transientcontent',
3843 'limitreport-timingprofile',
3851 if ( $hookRunner->onParserLimitReportFormat( $key, $value, $limitReport,
true,
true ) ) {
3854 if ( !$valueMsg->exists() ) {
3858 $valueMsg = (
new RawMessage(
'$1' ) )->params( $value );
3862 $valueMsg = $valueMsg->numParams( $value );
3864 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3865 $limitReport .= Html::openElement(
'tr' ) .
3866 Html::rawElement(
'th', [], $keyMsg->parse() ) .
3867 Html::rawElement(
'td', [], $valueMsg->parse() ) .
3868 Html::closeElement(
'tr' );
3873 $limitReport .= Html::closeElement(
'tbody' ) .
3874 Html::closeElement(
'table' ) .
3875 Html::closeElement(
'div' );
3877 return $limitReport;
3881 $out = $this->context->getOutput();
3882 $out->addHTML(
"<div class='editOptions'>\n" );
3884 if ( $this->section !==
'new' ) {
3885 $this->showSummaryInput(
false );
3886 $out->addHTML( $this->getSummaryPreview(
false ) );
3892 $expiryFromRequest =
null;
3893 if ( $this->preview || $this->diff || $this->isConflict ) {
3894 $expiryFromRequest = $this->getContext()->getRequest()->getText(
'wpWatchlistExpiry' );
3897 $checkboxes = $this->getCheckboxesWidget(
3899 [
'minor' => $this->minoredit,
'watch' => $this->watchthis,
'wpWatchlistExpiry' => $expiryFromRequest ]
3901 $checkboxesHTML =
new OOUI\HorizontalLayout( [
'items' => array_values( $checkboxes ) ] );
3903 $out->addHTML(
"<div class='editCheckboxes'>" . $checkboxesHTML .
"</div>\n" );
3906 $out->addHTML( self::getCopyrightWarning( $this->mTitle,
'parse', $this->context ) );
3907 $out->addHTML( $this->editFormTextAfterWarn );
3909 $out->addHTML(
"<div class='editButtons'>\n" );
3910 $out->addHTML( implode(
"\n", $this->getEditButtons( $tabindex ) ) .
"\n" );
3912 $cancel = $this->getCancelLink( $tabindex++ );
3914 $edithelp = $this->getHelpLink() .
3915 $this->context->msg(
'word-separator' )->escaped() .
3916 $this->context->msg(
'newwindow' )->parse();
3918 $out->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3919 $out->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3920 $out->addHTML(
"</div><!-- editButtons -->\n" );
3922 $this->getHookRunner()->onEditPage__showStandardInputs_options( $this, $out, $tabindex );
3924 $out->addHTML(
"</div><!-- editOptions -->\n" );
3931 private function showConflict(): void {
3932 $out = $this->context->getOutput();
3933 if ( $this->getHookRunner()->onEditPageBeforeConflictDiff( $this, $out ) ) {
3934 $this->incrementConflictStats();
3936 $this->getEditConflictHelper()->showEditFormTextAfterFooters();
3940 private function incrementConflictStats(): void {
3941 $this->getEditConflictHelper()->incrementConflictStats( $this->context->getUser() );
3944 private function getHelpLink(): string {
3945 $message = $this->context->
msg(
'edithelppage' )->inContentLanguage()->text();
3946 $editHelpUrl = Skin::makeInternalOrExternalUrl( $message );
3947 return Html::element(
'a', [
3948 'href' => $editHelpUrl,
3949 'target' =>
'helpwindow'
3950 ], $this->context->msg(
'edithelp' )->text() );
3957 private function getCancelLink(
int $tabindex ): ButtonWidget {
3959 if ( !$this->isConflict && $this->oldid > 0 ) {
3960 $cancelParams[
'oldid'] = $this->oldid;
3961 } elseif ( $this->getContextTitle()->isRedirect() ) {
3962 $cancelParams[
'redirect'] =
'no';
3965 return new OOUI\ButtonWidget( [
3966 'id' =>
'mw-editform-cancel',
3967 'tabIndex' => $tabindex,
3968 'href' => $this->getContextTitle()->getLinkURL( $cancelParams ),
3969 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
'cancel' )->parse() ),
3971 'infusable' =>
true,
3972 'flags' =>
'destructive',
3986 $request = $this->context->getRequest();
3989 $allowedFormParams = [
3990 'section',
'oldid',
'preloadtitle',
'undo',
'undoafter',
3992 'uselang',
'useskin',
'useformat',
'variant',
'debug',
'safemode'
3994 $formParams = [
'action' => $this->action ];
3995 foreach ( $params as $arg => $val ) {
3996 if ( in_array( $arg, $allowedFormParams,
true ) ) {
3997 $formParams[$arg] = $val;
4010 private function wasDeletedSinceLastEdit(): bool {
4011 if ( $this->deletedSinceEdit !== null ) {
4012 return $this->deletedSinceEdit;
4015 $this->deletedSinceEdit =
false;
4017 if ( !$this->mTitle->exists() && $this->mTitle->hasDeletedEdits() ) {
4018 $this->lastDelete = $this->getLastDelete();
4019 if ( $this->lastDelete ) {
4020 $deleteTime =
wfTimestamp( TS::MW, $this->lastDelete->log_timestamp );
4021 if ( $deleteTime > $this->starttime ) {
4022 $this->deletedSinceEdit =
true;
4027 return $this->deletedSinceEdit;
4035 private function getLastDelete(): ?stdClass {
4036 $dbr = $this->dbProvider->getReplicaDatabase();
4037 $commentQuery = $this->commentStore->getJoin(
'log_comment' );
4038 $data = $dbr->newSelectQueryBuilder()
4050 ->join(
'actor',
null,
'actor_id=log_actor' )
4052 'log_namespace' => $this->mTitle->getNamespace(),
4053 'log_title' => $this->mTitle->getDBkey(),
4054 'log_type' =>
'delete',
4055 'log_action' =>
'delete',
4057 ->orderBy( [
'log_timestamp',
'log_id' ], SelectQueryBuilder::SORT_DESC )
4058 ->queryInfo( $commentQuery )
4059 ->caller( __METHOD__ )
4062 if ( $data !==
false ) {
4063 if ( $data->log_deleted & LogPage::DELETED_USER ) {
4064 $data->actor_name = $this->context->msg(
'rev-deleted-user' )->escaped();
4067 if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
4068 $data->log_comment_text = $this->context->msg(
'rev-deleted-comment' )->escaped();
4069 $data->log_comment_data =
null;
4073 return $data ?:
null;
4082 $out = $this->context->getOutput();
4083 $config = $this->context->getConfig();
4085 if ( $config->get( MainConfigNames::RawHtml ) && !$this->mTokenOk ) {
4089 if ( $this->textbox1 !==
'' ) {
4093 $parsedNote = Html::rawElement(
'div', [
'class' =>
'previewnote' ],
4094 $out->parseAsInterface(
4095 $this->context->msg(
'session_fail_preview_html' )->plain()
4098 $this->incrementEditFailureStats(
'session_loss' );
4105 $content = $this->toEditContent( $this->textbox1 );
4108 if ( !$this->getHookRunner()->onAlternateEditPreview(
4109 $this, $content, $previewHTML, $this->mParserOutput )
4111 return $previewHTML;
4114 # provide a anchor link to the editform
4115 $continueEditing =
'<span class="mw-continue-editing">' .
4116 '[[#' . self::EDITFORM_ID .
'|' .
4117 $this->context->getLanguage()->getArrow() .
' ' .
4118 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
4119 if ( $this->mTriedSave && !$this->mTokenOk ) {
4120 $note = $this->context->msg(
'session_fail_preview' )->plain();
4121 $this->incrementEditFailureStats(
'session_loss' );
4122 } elseif ( $this->incompleteForm ) {
4123 $note = $this->context->msg(
'edit_form_incomplete' )->plain();
4124 if ( $this->mTriedSave ) {
4125 $this->incrementEditFailureStats(
'incomplete_form' );
4128 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
4131 # don't parse non-wikitext pages, show message about preview
4132 if ( $this->mTitle->isUserConfigPage() || $this->mTitle->isSiteConfigPage() ) {
4133 if ( $this->mTitle->isUserConfigPage() ) {
4135 } elseif ( $this->mTitle->isSiteConfigPage() ) {
4143 if ( $level ===
'user' && !$config->get( MainConfigNames::AllowUserCss ) ) {
4148 if ( $level ===
'user' ) {
4153 if ( $level ===
'user' && !$config->get( MainConfigNames::AllowUserJs ) ) {
4158 if ( $level ===
'user' && !$config->get( MainConfigNames::AllowUserJs ) ) {
4165 # Used messages to make sure grep find them:
4166 # Messages: usercsspreview, userjsonpreview, userjspreview,
4167 # sitecsspreview, sitejsonpreview, sitejspreview
4168 if ( $level && $format ) {
4169 $note =
"<div id='mw-{$level}{$format}preview'>" .
4170 $this->context->msg(
"{$level}{$format}preview" )->plain() .
4171 ' ' . $continueEditing .
"</div>";
4175 if ( $this->section ===
"new" ) {
4176 $content = $content->addSectionHeader( $this->sectiontitle );
4180 $this->getHookRunner()->onEditPageGetPreviewContent( $this, $content );
4182 $parserResult = $this->doPreviewParse( $content );
4183 $parserOutput = $parserResult[
'parserOutput'];
4184 $previewHTML = $parserResult[
'html'];
4185 $this->mParserOutput = $parserOutput;
4186 $out->addParserOutputMetadata( $parserOutput );
4187 if ( $out->userCanPreview() ) {
4188 $out->addContentOverride( $this->getTitle(), $content );
4191 foreach ( $parserOutput->getWarningMsgs() as $mv ) {
4192 $note .=
"\n\n" . $this->context->msg( $mv )->text();
4196 $m = $this->context->msg(
4197 'content-failed-to-parse',
4198 $this->contentModel,
4199 $this->contentFormat,
4202 $note .=
"\n\n" . $m->plain(); # gets parsed down below
4206 if ( $this->isConflict ) {
4207 $conflict = Html::warningBox(
4208 $this->context->msg(
'previewconflict' )->escaped(),
4209 'mw-previewconflict'
4215 $previewhead = Html::rawElement(
4216 'div', [
'class' =>
'previewnote' ],
4218 'h2', [
'id' =>
'mw-previewheader' ],
4219 $this->context->msg(
'preview' )->escaped()
4222 $out->parseAsInterface( $note )
4226 return $previewhead . $previewHTML . $this->previewTextAfterContent;
4229 private function incrementEditFailureStats(
string $failureType ): void {
4231 ->getCounter(
'edit_failure_total' )
4232 ->setLabel(
'cause', $failureType )
4233 ->setLabel(
'namespace',
'n/a' )
4234 ->setLabel(
'user_bucket',
'n/a' )
4243 $parserOptions = $this->page->makeParserOptions( $this->context );
4244 $parserOptions->setRenderReason(
'page-preview' );
4245 $parserOptions->setIsPreview(
true );
4246 $parserOptions->setIsSectionPreview( $this->section !==
null && $this->section !==
'' );
4253 return $parserOptions;
4266 $user = $this->getUserForPreview();
4267 $parserOptions = $this->getPreviewParserOptions();
4274 $services = MediaWikiServices::getInstance();
4277 $pstContent = $contentTransformer->preSaveTransform( $content, $this->mTitle, $user, $parserOptions );
4278 $parserOutput = $contentRenderer->getParserOutput( $pstContent, $this->mTitle,
null, $parserOptions );
4279 $out = $this->context->getOutput();
4280 $skin = $out->getSkin();
4281 $skinOptions = $skin->getOptions();
4284 $oldHtml = $parserOutput->getRawText();
4285 $html = $parserOutput->runOutputPipeline( $parserOptions, [
4286 'allowClone' =>
'false',
4287 'userLang' => $skin->getLanguage(),
4288 'injectTOC' => $skinOptions[
'toc'],
4289 'enableSectionEditLinks' =>
false,
4290 'includeDebugInfo' =>
true,
4291 ] )->getContentHolderText();
4292 $parserOutput->setRawText( $oldHtml );
4294 'parserOutput' => $parserOutput,
4303 if ( $this->preview || $this->section !==
'' ) {
4305 if ( !$this->mParserOutput ) {
4309 $this->mParserOutput->getLinkList( ParserOutputLinkTypes::TEMPLATE )
4310 as [
'link' => $link ]
4312 $templates[] = Title::newFromLinkTarget( $link );
4316 return $this->mTitle->getTemplateLinksFrom();
4326 $startingToolbar =
'<div id="toolbar"></div>';
4327 $toolbar = $startingToolbar;
4329 $hookRunner =
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
4330 if ( !$hookRunner->onEditPageBeforeEditToolbar( $toolbar ) ) {
4334 return ( $toolbar === $startingToolbar ) ? null : $toolbar;
4365 $user = $this->context->getUser();
4367 if ( !$this->isNew && $this->permManager->userHasRight( $user,
'minoredit' ) ) {
4368 $checkboxes[
'wpMinoredit'] = [
4369 'id' =>
'wpMinoredit',
4370 'label-message' =>
'minoredit',
4372 'tooltip' =>
'minoredit',
4373 'label-id' =>
'mw-editpage-minoredit',
4374 'legacy-name' =>
'minor',
4375 'default' => $values[
'minor'],
4379 if ( $user->isNamed() ) {
4380 $checkboxes = array_merge(
4382 $this->getCheckboxesDefinitionForWatchlist( $values[
'watch'], $values[
'wpWatchlistExpiry'] ??
null )
4386 $this->getHookRunner()->onEditPageGetCheckboxesDefinition( $this, $checkboxes );
4398 private function getCheckboxesDefinitionForWatchlist( $watch, $watchexpiry ): array {
4401 'id' =>
'wpWatchthis',
4402 'label-message' =>
'watchthis',
4404 'tooltip' =>
'watch',
4405 'label-id' =>
'mw-editpage-watch',
4406 'legacy-name' =>
'watch',
4407 'default' => $watch,
4410 if ( $this->watchlistExpiryEnabled ) {
4411 $watchedItem = $this->watchedItemStore->getWatchedItem( $this->getContext()->getUser(), $this->getTitle() );
4414 $userPreferredExpiry =
'infinite';
4416 $userPreferredExpiryOption = !$this->getTitle()->exists()
4417 ?
'watchcreations-expiry'
4418 :
'watchdefault-expiry';
4419 $userPreferredExpiry = $this->userOptionsLookup->getOption(
4420 $this->getContext()->getUser(),
4421 $userPreferredExpiryOption,
4426 $expiryOptions = WatchAction::getExpiryOptions(
4427 $this->getContext(),
4429 $userPreferredExpiry
4432 if ( $watchexpiry && in_array( $watchexpiry, $expiryOptions[
'options'] ) ) {
4433 $expiryOptions[
'default'] = $watchexpiry;
4438 $expiryFromRequest = $this->
getContext()->getRequest()->getText(
'wpWatchlistExpiry' );
4439 if ( ( $this->preview || $this->diff ) && in_array( $expiryFromRequest, $expiryOptions[
'options'] ) ) {
4440 $expiryOptions[
'default'] = $expiryFromRequest;
4445 foreach ( $expiryOptions[
'options'] as $label => $value ) {
4446 $options[] = [
'data' => $value,
'label' => $label ];
4449 $fieldDefs[
'wpWatchlistExpiry'] = [
4450 'id' =>
'wpWatchlistExpiry',
4451 'label-message' =>
'confirm-watch-label',
4453 'tooltip' =>
'watchlist-expiry',
4454 'label-id' =>
'mw-editpage-watchlist-expiry',
4455 'default' => $expiryOptions[
'default'],
4456 'value-attr' =>
'value',
4457 'class' => DropdownInputWidget::class,
4458 'options' => $options,
4459 'invisibleLabel' =>
true,
4477 $checkboxesDef = $this->getCheckboxesDefinition( $values );
4479 foreach ( $checkboxesDef as $name => $options ) {
4480 $legacyName = $options[
'legacy-name'] ?? $name;
4484 if ( isset( $options[
'tooltip'] ) ) {
4485 $accesskey = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4486 $title = Linker::titleAttrib( $options[
'tooltip'] );
4488 if ( isset( $options[
'title-message'] ) ) {
4489 $title = $this->context->msg( $options[
'title-message'] )->text();
4493 $className = $options[
'class'] ?? CheckboxInputWidget::class;
4494 $valueAttr = $options[
'value-attr'] ??
'selected';
4495 $checkboxes[ $legacyName ] =
new FieldLayout(
4497 'tabIndex' => ++$tabindex,
4498 'accessKey' => $accesskey,
4499 'id' => $options[
'id'] .
'Widget',
4500 'inputId' => $options[
'id'],
4502 $valueAttr => $options[
'default'],
4503 'infusable' =>
true,
4504 'options' => $options[
'options'] ??
null,
4507 'align' =>
'inline',
4508 'label' =>
new OOUI\HtmlSnippet( $this->context->msg( $options[
'label-message'] )->parse() ),
4510 'id' => $options[
'label-id'] ??
null,
4511 'invisibleLabel' => $options[
'invisibleLabel'] ??
null,
4522 private function getSubmitButtonLabel(): string {
4524 $this->context->getConfig()->get(
MainConfigNames::EditSubmitButtonLabelPublish );
4527 $newPage = !$this->mTitle->exists();
4529 if ( $labelAsPublish ) {
4530 $buttonLabelKey = $newPage ?
'publishpage' :
'publishchanges';
4532 $buttonLabelKey = $newPage ?
'savearticle' :
'savechanges';
4535 return $buttonLabelKey;
4552 $this->context->getConfig()->get( MainConfigNames::EditSubmitButtonLabelPublish );
4554 $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
4555 $buttonTooltip = $labelAsPublish ?
'publish' :
'save';
4557 $buttons[
'save'] =
new OOUI\ButtonInputWidget( [
4559 'tabIndex' => ++$tabindex,
4560 'id' =>
'wpSaveWidget',
4561 'inputId' =>
'wpSave',
4563 'useInputTag' =>
true,
4564 'flags' => [
'progressive',
'primary' ],
4565 'label' => $buttonLabel,
4566 'infusable' =>
true,
4569 'title' => Linker::titleAttrib( $buttonTooltip ),
4571 'accessKey' => Linker::accesskey( $buttonTooltip ),
4574 $buttons[
'preview'] =
new OOUI\ButtonInputWidget( [
4575 'name' =>
'wpPreview',
4576 'tabIndex' => ++$tabindex,
4577 'id' =>
'wpPreviewWidget',
4578 'inputId' =>
'wpPreview',
4580 'useInputTag' =>
true,
4581 'label' => $this->context->msg(
'showpreview' )->text(),
4582 'infusable' =>
true,
4585 'formNoValidate' =>
true,
4587 'title' => Linker::titleAttrib(
'preview' ),
4589 'accessKey' => Linker::accesskey(
'preview' ),
4592 $buttons[
'diff'] =
new OOUI\ButtonInputWidget( [
4594 'tabIndex' => ++$tabindex,
4595 'id' =>
'wpDiffWidget',
4596 'inputId' =>
'wpDiff',
4598 'useInputTag' =>
true,
4599 'label' => $this->context->msg(
'showdiff' )->text(),
4600 'infusable' =>
true,
4603 'formNoValidate' =>
true,
4605 'title' => Linker::titleAttrib(
'diff' ),
4607 'accessKey' => Linker::accesskey(
'diff' ),
4610 $this->getHookRunner()->onEditPageBeforeEditButtons( $this, $buttons, $tabindex );
4619 private function noSuchSectionPage(): void {
4620 $out = $this->context->getOutput();
4621 $out->prepareErrorPage();
4622 $out->setPageTitleMsg( $this->context->msg(
'nosuchsectiontitle' ) );
4624 $res = $this->context->msg(
'nosuchsectiontext', $this->section )->parseAsBlock();
4626 $this->getHookRunner()->onEditPageNoSuchSection( $this, $res );
4627 $out->addHTML( $res );
4629 $out->returnToMain(
false, $this->mTitle );
4638 $this->textbox2 = $this->textbox1;
4640 $out = $this->context->getOutput();
4641 $out->prepareErrorPage();
4642 $out->setPageTitleMsg( $this->context->msg(
'spamprotectiontitle' ) );
4644 $spamText = $this->context->msg(
'spamprotectiontext' )->parseAsBlock();
4647 if ( is_array( $match ) ) {
4650 $matchText = $this->context->getLanguage()->listToText( array_map(
'wfEscapeWikiText', $match ) );
4655 $spamText .= $this->context->msg(
'spamprotectionmatch' )
4656 ->params( $matchText )
4659 $out->addHTML( Html::rawElement(
4661 [
'id' =>
'spamprotected' ],
4665 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
4668 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
4669 $this->showTextbox( $this->textbox2,
'wpTextbox2', [
'tabindex' => 6,
'readonly' ] );
4671 $out->addReturnTo( $this->getContextTitle(), [
'action' =>
'edit' ] );
4674 private function addLongPageWarningHeader(): void {
4675 if ( $this->contentLength === false ) {
4676 $this->contentLength = strlen( $this->textbox1 );
4679 $out = $this->context->getOutput();
4680 $longPageHint = $this->context->msg(
'longpage-hint' );
4681 if ( !$longPageHint->isDisabled() ) {
4682 $msgText = trim( $longPageHint->sizeParams( $this->contentLength )
4683 ->params( $this->contentLength )
4685 if ( $msgText !==
'' && $msgText !==
'-' ) {
4686 $out->addHTML(
"<div id='mw-edit-longpage-hint'>\n$msgText\n</div>" );
4691 private function addExplainConflictHeader(): void {
4693 $this->getEditConflictHelper()->getExplainHeader()
4703 private function guessSectionName( $text ): string {
4704 $parser = MediaWikiServices::getInstance()->getParser();
4705 $name = $parser->guessSectionNameFromWikiText( $text );
4708 return '#' . urlencode( mb_substr( $name, 1 ) );
4716 Assert::precondition( !$this->editConflictHelperFactory,
4717 'Can only have one extension that resolves edit conflicts' );
4718 $this->editConflictHelperFactory = $factory;
4722 if ( !$this->editConflictHelper ) {
4723 $label = $this->getSubmitButtonLabel();
4724 if ( $this->editConflictHelperFactory ) {
4725 $this->editConflictHelper = ( $this->editConflictHelperFactory )( $label );
4727 $this->editConflictHelper =
new TextConflictHelper(
4729 $this->getContext()->getOutput(),
4730 MediaWikiServices::getInstance()->getStatsFactory(),
4732 MediaWikiServices::getInstance()->getContentHandlerFactory()
4736 return $this->editConflictHelper;
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'sodipodi'=> ' $path/sodipodi -z -w $width -f $input -e $output', 'inkscape'=> ' $path/inkscape -z -w $width -f $input -e $output', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'imgserv'=> ' $path/imgserv-wrapper -i svg -o png -w$width $input $output', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> false, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'BlockTargetMigrationStage' => 768, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'BlockTargetMigrationStage' => 'integer', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]