28use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36use OOUI\CheckboxInputWidget;
37use OOUI\DropdownInputWidget;
40use Wikimedia\ScopedCallback;
64 use ProtectedHookAccessorTrait;
378 # Placeholders for text injection by hooks (must be HTML)
379 # extensions should take care to _append_ to the present value
457 $this->mArticle = $article;
458 $this->page = $article->
getPage();
459 $this->mTitle = $article->
getTitle();
467 $this->context->setWikiPage( $this->page );
468 $this->context->setTitle( $this->mTitle );
470 $this->contentModel = $this->mTitle->getContentModel();
472 $services = MediaWikiServices::getInstance();
473 $this->contentHandlerFactory = $services->getContentHandlerFactory();
474 $this->contentFormat = $this->contentHandlerFactory
475 ->getContentHandler( $this->contentModel )
476 ->getDefaultFormat();
477 $this->editConflictHelperFactory = [ $this,
'newTextConflictHelper' ];
478 $this->permManager = $services->getPermissionManager();
479 $this->revisionStore = $services->getRevisionStore();
480 $this->watchlistExpiryEnabled = $this->
getContext()->getConfig() instanceof
Config
481 && $this->
getContext()->getConfig()->
get(
'WatchlistExpiry' );
482 $this->watchedItemStore = $services->getWatchedItemStore();
516 $this->mContextTitle =
$title;
528 if ( $this->mContextTitle ===
null ) {
529 wfDeprecated( get_class( $this ) .
'::getContextTitle called with no title set',
'1.32' );
545 return $this->enableApiEditOverride ===
true ||
546 $this->contentHandlerFactory->getContentHandler( $modelId )->supportsDirectEditing();
556 $this->enableApiEditOverride = $enableOverride;
572 if ( !$this->getHookRunner()->onAlternateEdit( $this ) ) {
576 wfDebug( __METHOD__ .
": enter" );
578 $request = $this->context->getRequest();
580 if ( $request->getBool(
'redlink' ) && $this->mTitle->exists() ) {
581 $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
586 $this->firsttime =
false;
591 $this->preview =
true;
595 $this->formtype =
'save';
596 } elseif ( $this->preview ) {
597 $this->formtype =
'preview';
598 } elseif ( $this->diff ) {
599 $this->formtype =
'diff';
600 }
else { # First time through
601 $this->firsttime =
true;
603 $this->formtype =
'preview';
605 $this->formtype =
'initial';
610 $this->save ? PermissionManager::RIGOR_SECURE : PermissionManager::RIGOR_FULL
613 wfDebug( __METHOD__ .
": User can't edit" );
615 if ( $this->context->getUser()->getBlock() ) {
618 DeferredUpdates::addCallableUpdate(
function () {
619 $this->context->getUser()->spreadAnyEditBlock();
628 $revRecord = $this->mArticle->fetchRevisionRecord();
631 $revContentModel = $revRecord ?
632 $revRecord->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel() :
634 if ( $revContentModel && $revContentModel !== $this->contentModel ) {
636 if ( $this->undidRev ) {
637 $undidRevRecord = $this->revisionStore
638 ->getRevisionById( $this->undidRev );
639 $prevRevRecord = $undidRevRecord ?
640 $this->revisionStore->getPreviousRevision( $undidRevRecord ) :
643 $prevContentModel = $prevRevRecord ?
645 ->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )
650 if ( !$this->undidRev
652 || $prevContentModel !== $this->contentModel
657 'contentmodelediterror',
666 $this->isConflict =
false;
668 # Show applicable editing introductions
669 if ( $this->formtype ==
'initial' || $this->firsttime ) {
673 # Attempt submission here. This will check for edit conflicts,
674 # and redundantly check for locked database, blocked IPs, etc.
675 # that edit() already checked just in case someone tries to sneak
676 # in the back door with a hand-edited submission URL.
678 if ( $this->formtype ==
'save' ) {
679 $resultDetails =
null;
681 if ( !$this->
handleStatus( $status, $resultDetails ) ) {
686 # First time through: get contents, set time for conflict
688 if ( $this->formtype ==
'initial' || $this->firsttime ) {
693 if ( !$this->mTitle->getArticleID() ) {
694 $this->getHookRunner()->onEditFormPreloadText( $this->textbox1, $this->mTitle );
696 $this->getHookRunner()->onEditFormInitialText( $this );
709 $user = $this->context->getUser();
710 $permErrors = $this->permManager->getPermissionErrors(
716 # Can this title be created?
717 if ( !$this->mTitle->exists() ) {
718 $permErrors = array_merge(
721 $this->permManager->getPermissionErrors(
731 # Ignore some permissions errors when a user is just previewing/viewing diffs
733 foreach ( $permErrors as $error ) {
734 if ( ( $this->preview || $this->diff )
736 $error[0] ==
'blockedtext' ||
737 $error[0] ==
'autoblockedtext' ||
738 $error[0] ==
'systemblockedtext'
762 $out = $this->context->getOutput();
763 if ( $this->context->getRequest()->getBool(
'redlink' ) ) {
767 $out->redirect( $this->mTitle->getFullURL() );
773 # Use the normal message if there's nothing to display
775 $action = $this->mTitle->exists() ?
'edit' :
776 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
782 $out->formatPermissionsErrorMessage( $permErrors,
'edit' )
792 $out = $this->context->getOutput();
793 $this->getHookRunner()->onEditPage__showReadOnlyForm_initial( $this, $out );
795 $out->setRobotPolicy(
'noindex,nofollow' );
796 $out->setPageTitle( $this->context->msg(
798 $this->getContextTitle()->getPrefixedText()
801 $out->addHTML( $this->editFormPageTop );
802 $out->addHTML( $this->editFormTextTop );
804 if ( $errorMessage !==
'' ) {
805 $out->addWikiTextAsInterface( $errorMessage );
806 $out->addHTML(
"<hr />\n" );
809 # If the user made changes, preserve them when showing the markup
810 # (This happens when a user is blocked during edit, for instance)
811 if ( !$this->firsttime ) {
813 $out->addWikiMsg(
'viewyourtext' );
818 # Serialize using the default format if the content model is not supported
819 # (e.g. for an old revision with a different model)
822 $out->addWikiMsg(
'viewsourcetext' );
825 $out->addHTML( $this->editFormTextBeforeContent );
826 $this->
showTextbox( $text,
'wpTextbox1', [
'readonly' ] );
827 $out->addHTML( $this->editFormTextAfterContent );
831 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
833 $out->addHTML( $this->editFormTextBottom );
834 if ( $this->mTitle->exists() ) {
835 $out->returnToMain(
null, $this->mTitle );
845 $config = $this->context->getConfig();
846 $previewOnOpenNamespaces = $config->get(
'PreviewOnOpenNamespaces' );
847 $request = $this->context->getRequest();
848 if ( $config->get(
'RawHtml' ) ) {
854 if ( $request->getVal(
'preview' ) ==
'yes' ) {
857 } elseif ( $request->getVal(
'preview' ) ==
'no' ) {
860 } elseif ( $this->section ==
'new' ) {
863 } elseif ( ( $request->getCheck(
'preload' ) || $this->mTitle->exists() )
864 && $this->context->getUser()->getOption(
'previewonfirst' )
868 } elseif ( !$this->mTitle->exists()
869 && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
870 && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
886 if ( $this->mTitle->isUserConfigPage() ) {
887 $name = $this->mTitle->getSkinFromConfigSubpage();
888 $skins = array_merge(
889 array_keys( Skin::getSkinNames() ),
892 return !in_array( $name, $skins )
893 && in_array( strtolower( $name ), $skins );
907 return $this->contentHandlerFactory
908 ->getContentHandler( $this->mTitle->getContentModel() )
909 ->supportsSections();
918 # Section edit can come from either the form or a link
919 $this->section = $request->getVal(
'wpSection', $request->getVal(
'section' ) );
922 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
925 $this->isNew = !$this->mTitle->exists() || $this->section ==
'new';
927 if ( $request->wasPosted() ) {
928 # These fields need to be checked for encoding.
929 # Also remove trailing whitespace, but don't remove _initial_
930 # whitespace from the text boxes. This may be significant formatting.
931 $this->textbox1 = rtrim( $request->getText(
'wpTextbox1' ) );
932 if ( !$request->getCheck(
'wpTextbox2' ) ) {
942 $this->unicodeCheck = $request->getText(
'wpUnicodeCheck' );
944 $this->summary = $request->getText(
'wpSummary' );
946 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
947 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
949 $this->summary = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->summary );
951 # Treat sectiontitle the same way as summary.
952 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
953 # currently doing double duty as both edit summary and section title. Right now this
954 # is just to allow API edits to work around this limitation, but this should be
955 # incorporated into the actual edit form when EditPage is rewritten (T20654, T28312).
956 $this->sectiontitle = $request->getText(
'wpSectionTitle' );
957 $this->sectiontitle = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->sectiontitle );
959 $this->edittime = $request->getVal(
'wpEdittime' );
960 $this->editRevId = $request->getIntOrNull(
'editRevId' );
961 $this->starttime = $request->getVal(
'wpStarttime' );
963 $undidRev = $request->getInt(
'wpUndidRevision' );
967 $undoAfter = $request->getInt(
'wpUndoAfter' );
972 $this->scrolltop = $request->getIntOrNull(
'wpScrolltop' );
974 if ( $this->textbox1 ===
'' && !$request->getCheck(
'wpTextbox1' ) ) {
978 $this->incompleteForm =
true;
982 $this->incompleteForm = !$request->getVal(
'wpUltimateParam' );
984 if ( $this->incompleteForm ) {
985 # If the form is incomplete, force to preview.
986 wfDebug( __METHOD__ .
": Form data appears to be incomplete" );
987 wfDebug(
"POST DATA: " . var_export( $request->getPostValues(),
true ) );
988 $this->preview =
true;
990 $this->preview = $request->getCheck(
'wpPreview' );
991 $this->diff = $request->getCheck(
'wpDiff' );
997 if ( $this->
tokenOk( $request ) ) {
998 # Some browsers will not report any submit button
999 # if the user hits enter in the comment box.
1000 # The unmarked state will be assumed to be a save,
1001 # if the form seems otherwise complete.
1002 wfDebug( __METHOD__ .
": Passed token check." );
1003 } elseif ( $this->diff ) {
1004 # Failed token check, but only requested "Show Changes".
1005 wfDebug( __METHOD__ .
": Failed token check; Show Changes requested." );
1007 # Page might be a hack attempt posted from
1008 # an external site. Preview instead of saving.
1009 wfDebug( __METHOD__ .
": Failed token check; forcing preview" );
1010 $this->preview =
true;
1014 if ( !$this->edittime || !preg_match(
'/^\d{14}$/', $this->edittime ) ) {
1015 $this->edittime =
null;
1018 if ( !$this->starttime || !preg_match(
'/^\d{14}$/', $this->starttime ) ) {
1019 $this->starttime =
null;
1022 $this->recreate = $request->getCheck(
'wpRecreate' );
1026 $this->minoredit = $request->getCheck(
'wpMinoredit' );
1027 $this->watchthis = $request->getCheck(
'wpWatchthis' );
1028 $expiry = $request->getText(
'wpWatchlistExpiry' );
1029 if ( $this->watchlistExpiryEnabled && $expiry !==
'' ) {
1034 $expiry = ExpiryDef::normalizeExpiry( $expiry, TS_ISO_8601 );
1035 if ( $expiry !==
false ) {
1036 $this->watchlistExpiry = $expiry;
1040 # Don't force edit summaries when a user is editing their own user or talk page
1041 if ( ( $this->mTitle->mNamespace ==
NS_USER || $this->mTitle->mNamespace ==
NS_USER_TALK )
1042 && $this->mTitle->getText() == $user->getName()
1044 $this->allowBlankSummary =
true;
1046 $this->allowBlankSummary = $request->getBool(
'wpIgnoreBlankSummary' )
1047 || !$user->getOption(
'forceeditsummary' );
1050 $this->autoSumm = $request->getText(
'wpAutoSummary' );
1052 $this->allowBlankArticle = $request->getBool(
'wpIgnoreBlankArticle' );
1053 $this->allowSelfRedirect = $request->getBool(
'wpIgnoreSelfRedirect' );
1057 $this->changeTags = [];
1059 $this->changeTags = array_filter( array_map(
'trim', explode(
',',
1063 # Not a posted form? Start with nothing.
1064 wfDebug( __METHOD__ .
": Not a posted form." );
1065 $this->textbox1 =
'';
1066 $this->summary =
'';
1067 $this->sectiontitle =
'';
1068 $this->edittime =
'';
1069 $this->editRevId =
null;
1071 $this->
edit =
false;
1072 $this->preview =
false;
1073 $this->save =
false;
1074 $this->diff =
false;
1075 $this->minoredit =
false;
1077 $this->watchthis = $request->getBool(
'watchthis',
false );
1078 if ( $this->watchlistExpiryEnabled ) {
1079 $this->watchlistExpiry =
null;
1081 $this->recreate =
false;
1085 if ( $this->section ==
'new' && $request->getVal(
'preloadtitle' ) ) {
1086 $this->sectiontitle = $request->getVal(
'preloadtitle' );
1088 $this->summary = $request->getVal(
'preloadtitle' );
1089 } elseif ( $this->section !=
'new' && $request->getVal(
'summary' ) !==
'' ) {
1090 $this->summary = $request->getText(
'summary' );
1091 if ( $this->summary !==
'' ) {
1092 $this->hasPresetSummary =
true;
1096 if ( $request->getVal(
'minor' ) ) {
1097 $this->minoredit =
true;
1101 $this->oldid = $request->getInt(
'oldid' );
1102 $this->parentRevId = $request->getInt(
'parentRevId' );
1104 $this->markAsBot = $request->getBool(
'bot',
true );
1105 $this->nosummary = $request->getBool(
'nosummary' );
1108 $this->contentModel = $request->getText(
'model', $this->contentModel );
1110 $this->contentFormat = $request->getText(
'format', $this->contentFormat );
1113 $handler = $this->contentHandlerFactory->getContentHandler( $this->contentModel );
1116 'editpage-invalidcontentmodel-title',
1117 'editpage-invalidcontentmodel-text',
1122 if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1124 'editpage-notsupportedcontentformat-title',
1125 'editpage-notsupportedcontentformat-text',
1128 wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1139 $this->editintro = $request->getText(
'editintro',
1141 $this->section ===
'new' ?
'MediaWiki:addsection-editintro' :
'' );
1144 $this->getHookRunner()->onEditPage__importFormData( $this, $request );
1166 $this->edittime = $this->page->getTimestamp();
1167 $this->editRevId = $this->page->getLatest();
1169 $dummy = $this->contentHandlerFactory
1170 ->getContentHandler( $this->contentModel )
1171 ->makeEmptyContent();
1179 $out = $this->context->getOutput();
1180 $this->editFormPageTop .= Html::rawElement(
1181 'div', [
'class' =>
'errorbox' ],
1182 $out->parseAsInterface( $this->context->msg(
'missing-revision-content',
1189 $modelName = $modelMsg->exists() ? $modelMsg->text() :
$content->getModel();
1191 $out = $this->context->getOutput();
1192 $out->showErrorPage(
1193 'modeleditnotsupported-title',
1194 'modeleditnotsupported-text',
1202 $user = $this->context->getUser();
1204 # Sort out the "watch" checkbox
1205 if ( $user->getOption(
'watchdefault' ) ) {
1207 $this->watchthis =
true;
1208 } elseif ( $user->getOption(
'watchcreations' ) && !$this->mTitle->exists() ) {
1210 $this->watchthis =
true;
1211 } elseif ( $user->isWatched( $this->mTitle ) ) {
1213 $this->watchthis =
true;
1215 if ( $this->watchthis && $this->watchlistExpiryEnabled ) {
1216 $watchedItem = $this->watchedItemStore->getWatchedItem( $user, $this->
getTitle() );
1217 $this->watchlistExpiry = $watchedItem ? $watchedItem->getExpiry() :
null;
1219 if ( $user->getOption(
'minordefault' ) && !$this->isNew ) {
1220 $this->minoredit =
true;
1222 if ( $this->textbox1 ===
false ) {
1240 $user = $this->context->getUser();
1241 $request = $this->context->getRequest();
1244 if ( !$this->mTitle->exists() || $this->section ==
'new' ) {
1245 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && $this->section !=
'new' ) {
1246 # If this is a system message, get the default text.
1247 $msg = $this->mTitle->getDefaultMessageText();
1252 # If requested, preload some text.
1253 $preload = $request->getVal(
'preload',
1255 $this->section ===
'new' ?
'MediaWiki:addsection-preload' :
'' );
1256 $params = $request->getArray(
'preloadparams', [] );
1261 } elseif ( $this->section !=
'' ) {
1264 $content = $orig ? $orig->getSection( $this->section ) :
null;
1270 $undoafter = $request->getInt(
'undoafter' );
1271 $undo = $request->getInt(
'undo' );
1273 if ( $undo > 0 && $undoafter > 0 ) {
1276 $undorev = $this->revisionStore->getRevisionByTitle( $this->mTitle, $undo );
1277 $oldrev = $this->revisionStore->getRevisionByTitle( $this->mTitle, $undoafter );
1280 # Sanity check, make sure it's the right page,
1281 # the revisions exist and they were not deleted.
1282 # Otherwise, $content will be left as-is.
1283 if ( $undorev !==
null && $oldrev !==
null &&
1284 !$undorev->isDeleted( RevisionRecord::DELETED_TEXT ) &&
1285 !$oldrev->isDeleted( RevisionRecord::DELETED_TEXT )
1287 if ( WikiPage::hasDifferencesOutsideMainSlot( $undorev, $oldrev )
1289 $oldrev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
1293 $this->context->getOutput()->redirect( $this->mTitle->getFullURL( [
1294 'action' =>
'mcrundo',
1296 'undoafter' => $undoafter,
1300 $handler = $this->contentHandlerFactory
1301 ->getContentHandler( $undorev->getSlot(
1305 $currentContent = $this->page->getRevisionRecord()
1306 ->getContent( SlotRecord::MAIN );
1307 $undoContent = $undorev->getContent( SlotRecord::MAIN );
1308 $undoAfterContent = $oldrev->getContent( SlotRecord::MAIN );
1309 $undoIsLatest = $this->page->getRevisionRecord()->getId() === $undorev->getId();
1310 $content = $handler->getUndoContent(
1318 # Warn the user that something went wrong
1319 $undoMsg =
'failure';
1323 if ( $undoMsg ===
null ) {
1324 $oldContent = $this->page->getContent( RevisionRecord::RAW );
1325 $popts = ParserOptions::newFromUserAndLang(
1326 $user, MediaWikiServices::getInstance()->getContentLanguage() );
1327 $newContent =
$content->preSaveTransform( $this->mTitle, $user, $popts );
1328 if ( $newContent->getModel() !== $oldContent->getModel() ) {
1333 $this->contentModel = $newContent->getModel();
1334 $oldMainSlot = $oldrev->getSlot(
1338 $this->contentFormat = $oldMainSlot->getFormat();
1339 if ( $this->contentFormat ===
null ) {
1340 $this->contentFormat = $this->contentHandlerFactory
1341 ->getContentHandler( $oldMainSlot->getModel() )
1342 ->getDefaultFormat();
1346 if ( $newContent->equals( $oldContent ) ) {
1347 # Tell the user that the undo results in no change,
1348 # i.e. the revisions were already undone.
1349 $undoMsg =
'nochange';
1352 # Inform the user of our success and set an automatic edit summary
1353 $undoMsg =
'success';
1355 # If we just undid one rev, use an autosummary
1356 $firstrev = $this->revisionStore->getNextRevision( $oldrev );
1357 if ( $firstrev && $firstrev->getId() == $undo ) {
1358 $userText = $undorev->getUser() ?
1359 $undorev->getUser()->getName() :
1361 if ( $userText ===
'' ) {
1362 $undoSummary = $this->context->msg(
1363 'undo-summary-username-hidden',
1365 )->inContentLanguage()->text();
1367 } elseif ( ExternalUserNames::isExternal( $userText ) ) {
1368 $userLinkTitle = ExternalUserNames::getUserLinkTitle( $userText );
1369 if ( $userLinkTitle ) {
1370 $userLink = $userLinkTitle->getPrefixedText();
1371 $undoSummary = $this->context->msg(
1372 'undo-summary-import',
1376 )->inContentLanguage()->text();
1378 $undoSummary = $this->context->msg(
1379 'undo-summary-import2',
1382 )->inContentLanguage()->text();
1385 $undoIsAnon = $undorev->getUser() ?
1386 !$undorev->getUser()->isRegistered() :
1389 'undo-summary-anon' :
1391 $undoSummary = $this->context->msg(
1395 )->inContentLanguage()->text();
1397 if ( $this->summary ===
'' ) {
1398 $this->summary = $undoSummary;
1400 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1404 $this->undidRev = $undo;
1405 $this->undoAfter = $undoafter;
1406 $this->formtype =
'diff';
1416 $out = $this->context->getOutput();
1419 $class = ( $undoMsg ==
'success' ?
'' :
'error ' ) .
"mw-undo-{$undoMsg}";
1420 $this->editFormPageTop .= Html::rawElement(
1421 'div', [
'class' => $class ],
1422 $out->parseAsInterface(
1423 $this->context->msg(
'undo-' . $undoMsg )->plain()
1431 $curRevisionRecord = $this->page->getRevisionRecord();
1432 $oldRevisionRecord = $this->mArticle->fetchRevisionRecord();
1434 if ( $curRevisionRecord
1435 && $oldRevisionRecord
1436 && $curRevisionRecord->getId() !== $oldRevisionRecord->getId()
1437 && ( WikiPage::hasDifferencesOutsideMainSlot(
1440 ) || !$this->isSupportedContentModel(
1441 $oldRevisionRecord->getSlot(
1447 $this->context->getOutput()->redirect(
1448 $this->mTitle->getFullURL(
1450 'action' =>
'mcrrestore',
1451 'restore' => $oldRevisionRecord->getId(),
1484 if ( $this->section ==
'new' ) {
1487 $revRecord = $this->mArticle->fetchRevisionRecord();
1488 if ( $revRecord ===
null ) {
1489 return $this->contentHandlerFactory
1490 ->getContentHandler( $this->contentModel )
1491 ->makeEmptyContent();
1493 return $revRecord->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER, $user );
1509 if ( $this->parentRevId ) {
1512 return $this->mArticle->getRevIdFetched();
1525 $revRecord = $this->page->getRevisionRecord();
1526 $content = $revRecord ? $revRecord->getContent(
1532 return $this->contentHandlerFactory
1533 ->getContentHandler( $this->contentModel )
1534 ->makeEmptyContent();
1535 } elseif ( !$this->undidRev ) {
1536 $mainSlot = $revRecord->getSlot( SlotRecord::MAIN, RevisionRecord::RAW );
1542 $logger = LoggerFactory::getInstance(
'editpage' );
1543 if ( $this->contentModel !== $mainSlot->getModel() ) {
1544 $logger->warning(
"Overriding content model from current edit {prev} to {new}", [
1545 'prev' => $this->contentModel,
1546 'new' => $mainSlot->getModel(),
1547 'title' => $this->getTitle()->getPrefixedDBkey(),
1548 'method' => __METHOD__
1550 $this->contentModel = $mainSlot->getModel();
1555 if ( !
$content->isSupportedFormat( $this->contentFormat ) ) {
1556 $revFormat = $mainSlot->getFormat();
1557 if ( $revFormat ===
null ) {
1558 $revFormat = $this->contentHandlerFactory
1559 ->getContentHandler( $mainSlot->getModel() )
1560 ->getDefaultFormat();
1563 $logger->warning(
"Current revision content format unsupported. Overriding {prev} to {new}", [
1564 'prev' => $this->contentFormat,
1565 'new' => $revFormat,
1566 'title' => $this->
getTitle()->getPrefixedDBkey(),
1567 'method' => __METHOD__
1569 $this->contentFormat = $revFormat;
1598 if ( !empty( $this->mPreloadContent ) ) {
1602 $handler = $this->contentHandlerFactory->getContentHandler( $this->contentModel );
1604 if ( $preload ===
'' ) {
1605 return $handler->makeEmptyContent();
1608 $user = $this->context->getUser();
1609 $title = Title::newFromText( $preload );
1611 # Check for existence to avoid getting MediaWiki:Noarticletext
1614 return $handler->makeEmptyContent();
1623 return $handler->makeEmptyContent();
1628 $parserOptions = ParserOptions::newFromUser( $user );
1633 return $handler->makeEmptyContent();
1636 if (
$content->getModel() !== $handler->getModelID() ) {
1637 $converted =
$content->convert( $handler->getModelID() );
1639 if ( !$converted ) {
1641 wfDebug(
"Attempt to preload incompatible content: " .
1642 "can't convert " .
$content->getModel() .
1643 " to " . $handler->getModelID() );
1645 return $handler->makeEmptyContent();
1651 return $content->preloadTransform(
$title, $parserOptions, $params );
1664 return $title &&
$title->exists() && $this->permManager->userCan(
'read', $user,
$title );
1675 $token = $request->getVal(
'wpEditToken' );
1676 $user = $this->context->getUser();
1677 $this->mTokenOk = $user->matchEditToken( $token );
1678 $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1697 $revisionId = $this->page->getLatest();
1698 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1701 if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1703 } elseif ( $this->oldid ) {
1707 $response = $this->context->getRequest()->response();
1708 $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1725 && $this->permManager->userHasRight( $this->context->getUser(),
'bot' );
1728 $this->getHookRunner()->onEditPage__attemptSave_after( $this, $status, $resultDetails );
1737 if ( $this->context->getRequest()->getText(
'mode' ) !==
'conflict' ) {
1758 if ( $status->value == self::AS_SUCCESS_UPDATE
1759 || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1763 $this->didSave =
true;
1764 if ( !$resultDetails[
'nullEdit'] ) {
1769 $out = $this->context->getOutput();
1773 $request = $this->context->getRequest();
1774 $extraQueryRedirect = $request->getVal(
'wpExtraQueryRedirect' );
1776 switch ( $status->value ) {
1794 __METHOD__ .
' with $status->value == AS_CANNOT_USE_CUSTOM_MODEL',
1800 $out->wrapWikiTextAsInterface(
'error',
1801 $status->
getWikiText(
false,
false, $this->context->getLanguage() )
1806 $query = $resultDetails[
'redirect'] ?
'redirect=no' :
'';
1807 if ( $extraQueryRedirect ) {
1808 if ( $query !==
'' ) {
1811 $query .= $extraQueryRedirect;
1813 $anchor = $resultDetails[
'sectionanchor'] ??
'';
1814 $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1819 $sectionanchor = $resultDetails[
'sectionanchor'];
1822 $this->getHookRunner()->onArticleUpdateBeforeRedirect( $this->mArticle,
1823 $sectionanchor, $extraQuery );
1825 if ( $resultDetails[
'redirect'] ) {
1826 if ( $extraQuery !==
'' ) {
1827 $extraQuery =
'&' . $extraQuery;
1829 $extraQuery =
'redirect=no' . $extraQuery;
1831 if ( $extraQueryRedirect ) {
1832 if ( $extraQuery !==
'' ) {
1835 $extraQuery .= $extraQueryRedirect;
1838 $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1847 $this->context->getUser()->getBlock(),
1848 $this->context->getUser(),
1849 $this->context->getLanguage(),
1868 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
1879 $this->hookError =
'<div class="error">' .
"\n" .
1880 $status->
getWikiText(
false,
false, $this->context->getLanguage() ) .
1897 if ( $this->hookError !=
'' ) {
1898 # ...or the hook could be expecting us to produce an error
1899 $status->
fatal(
'hookaborted' );
1905 if ( !$this->getHookRunner()->onEditFilterMergedContent( $this->context,
$content,
1906 $status, $this->summary, $user, $this->minoredit )
1908 # Error messages etc. could be handled within the hook...
1909 if ( $status->
isGood() ) {
1910 $status->
fatal(
'hookaborted' );
1919 if ( !$status->value ) {
1923 } elseif ( !$status->
isOK() ) {
1924 # ...or the hook could be expecting us to produce an error
1928 $status->
fatal(
'hookaborted' );
1948 $this->context->getLanguage()
1951<div
class=
"errorbox">
1965 if ( $this->sectiontitle !==
'' ) {
1970 if ( $this->summary ===
'' ) {
1971 $cleanSectionTitle = MediaWikiServices::getInstance()->getParser()
1972 ->stripSectionName( $this->sectiontitle );
1973 return $this->context->msg(
'newsectionsummary' )
1974 ->plaintextParams( $cleanSectionTitle )->inContentLanguage()->text();
1976 } elseif ( $this->summary !==
'' ) {
1978 # This is a new section, so create a link to the new section
1979 # in the revision summary.
1980 $cleanSummary = MediaWikiServices::getInstance()->getParser()
1981 ->stripSectionName( $this->summary );
1982 return $this->context->msg(
'newsectionsummary' )
1983 ->plaintextParams( $cleanSummary )->inContentLanguage()->text();
2014 $status = Status::newGood();
2015 $user = $this->context->getUser();
2017 if ( !$this->getHookRunner()->onEditPage__attemptSave( $this ) ) {
2018 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving" );
2019 $status->fatal(
'hookaborted' );
2024 if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
2025 $status->fatal(
'unicode-support-fail' );
2030 $request = $this->context->getRequest();
2031 $spam = $request->getText(
'wpAntispam' );
2032 if ( $spam !==
'' ) {
2037 $this->mTitle->getPrefixedText() .
2038 '" submitted bogus field "' .
2042 $status->fatal(
'spamprotectionmatch',
false );
2048 # Construct Content object
2052 'content-failed-to-parse',
2053 $this->contentModel,
2054 $this->contentFormat,
2061 # Check image redirect
2062 if ( $this->mTitle->getNamespace() ==
NS_FILE &&
2063 $textbox_content->isRedirect() &&
2064 !$this->permManager->userHasRight( $user,
'upload' )
2067 $status->setResult(
false, $code );
2073 $spamRegexChecker = MediaWikiServices::getInstance()->getSpamChecker();
2074 $match = $spamRegexChecker->checkSummary( $this->summary );
2075 if ( $match ===
false && $this->section ==
'new' ) {
2076 # $wgSpamRegex is enforced on this new heading/summary because, unlike
2077 # regular summaries, it is added to the actual wikitext.
2078 if ( $this->sectiontitle !==
'' ) {
2079 # This branch is taken when the API is used with the 'sectiontitle' parameter.
2080 $match = $spamRegexChecker->checkContent( $this->sectiontitle );
2082 # This branch is taken when the "Add Topic" user interface is used, or the API
2083 # is used with the 'summary' parameter.
2084 $match = $spamRegexChecker->checkContent( $this->summary );
2087 if ( $match ===
false ) {
2088 $match = $spamRegexChecker->checkContent( $this->textbox1 );
2090 if ( $match !==
false ) {
2091 $result[
'spam'] = $match;
2092 $ip = $request->getIP();
2093 $pdbk = $this->mTitle->getPrefixedDBkey();
2094 $match = str_replace(
"\n",
'', $match );
2095 wfDebugLog(
'SpamRegex',
"$ip spam regex hit [[$pdbk]]: \"$match\"" );
2096 $status->fatal(
'spamprotectionmatch', $match );
2100 if ( !$this->getHookRunner()->onEditFilter( $this, $this->textbox1, $this->section,
2101 $this->hookError, $this->summary )
2103 # Error messages etc. could be handled within the hook...
2104 $status->fatal(
'hookaborted' );
2107 } elseif ( $this->hookError !=
'' ) {
2108 # ...or the hook could be expecting us to produce an error
2109 $status->fatal(
'hookaborted' );
2114 if ( $this->permManager->isBlockedFrom( $user, $this->mTitle ) ) {
2117 $user->spreadAnyEditBlock();
2119 # Check block state against master, thus 'false'.
2120 $status->setResult(
false, self::AS_BLOCKED_PAGE_FOR_USER );
2124 $this->contentLength = strlen( $this->textbox1 );
2125 $config = $this->context->getConfig();
2126 $maxArticleSize = $config->get(
'MaxArticleSize' );
2127 if ( $this->contentLength > $maxArticleSize * 1024 ) {
2129 $this->tooBig =
true;
2130 $status->setResult(
false, self::AS_CONTENT_TOO_BIG );
2134 if ( !$this->permManager->userHasRight( $user,
'edit' ) ) {
2135 if ( $user->isAnon() ) {
2136 $status->setResult(
false, self::AS_READ_ONLY_PAGE_ANON );
2139 $status->fatal(
'readonlytext' );
2145 $changingContentModel =
false;
2146 if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
2147 if ( !$this->permManager->userHasRight( $user,
'editcontentmodel' ) ) {
2148 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
2155 $canEditModel = $this->permManager->userCan(
2158 $titleWithNewContentModel
2163 || !$this->permManager->userCan(
'edit', $user, $titleWithNewContentModel )
2165 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
2170 $changingContentModel =
true;
2171 $oldContentModel = $this->mTitle->getContentModel();
2174 if ( $this->changeTags ) {
2176 $this->changeTags, $user );
2177 if ( !$changeTagsStatus->isOK() ) {
2179 return $changeTagsStatus;
2184 $status->fatal(
'readonlytext' );
2188 if ( $user->pingLimiter() || $user->pingLimiter(
'linkpurge', 0 )
2189 || ( $changingContentModel && $user->pingLimiter(
'editcontentmodel' ) )
2191 $status->fatal(
'actionthrottledtext' );
2196 # If the article has been deleted while editing, don't save it without
2199 $status->setResult(
false, self::AS_ARTICLE_WAS_DELETED );
2203 # Load the page data from the master. If anything changes in the meantime,
2204 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
2205 $this->page->loadPageData(
'fromdbmaster' );
2206 $new = !$this->page->exists();
2210 if ( !$this->permManager->userCan(
'create', $user, $this->mTitle ) ) {
2211 $status->fatal(
'nocreatetext' );
2213 wfDebug( __METHOD__ .
": no create permission" );
2220 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2221 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI && $defaultMessageText !==
false ) {
2222 $defaultText = $defaultMessageText;
2227 if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
2228 $this->blankArticle =
true;
2229 $status->fatal(
'blankarticle' );
2230 $status->setResult(
false, self::AS_BLANK_ARTICLE );
2240 $result[
'sectionanchor'] =
'';
2241 if ( $this->section ==
'new' ) {
2243 if ( $this->sectiontitle !==
'' ) {
2246 } elseif ( $this->summary !==
'' ) {
2257 # Article exists. Check for edit conflict.
2259 $this->page->clear(); # Force reload of dates, etc.
2260 $timestamp = $this->page->getTimestamp();
2261 $latest = $this->page->getLatest();
2263 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}" );
2264 wfDebug(
"revision: {$latest}, editRevId: {$this->editRevId}" );
2270 if ( ( $this->edittime !==
null && $this->edittime != $timestamp )
2271 || ( $this->editRevId !==
null && $this->editRevId != $latest )
2273 $this->isConflict =
true;
2274 if ( $this->section ==
'new' ) {
2275 if ( $this->page->getUserText() == $user->getName() &&
2276 $this->page->getComment() == $this->newSectionSummary()
2282 .
": duplicate new section submission; trigger edit conflict!" );
2285 $this->isConflict =
false;
2286 wfDebug( __METHOD__ .
": conflict suppressed; new section" );
2288 } elseif ( $this->section ==
''
2290 && $this->revisionStore->userWasLastToEdit(
2292 $this->mTitle->getArticleID(),
2297 # Suppress edit conflict with self, except for section edits where merging is required.
2298 wfDebug( __METHOD__ .
": Suppressing edit conflict, same user." );
2299 $this->isConflict =
false;
2305 if ( $this->sectiontitle !==
'' ) {
2313 if ( $this->isConflict ) {
2315 .
": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2316 .
" (id '{$this->editRevId}') (article time '{$timestamp}')" );
2319 if ( $this->editRevId !==
null ) {
2320 $content = $this->page->replaceSectionAtRev(
2327 $content = $this->page->replaceSectionContent(
2335 wfDebug( __METHOD__ .
": getting section '{$this->section}'" );
2336 $content = $this->page->replaceSectionContent(
2344 wfDebug( __METHOD__ .
": activating conflict; section replace failed." );
2345 $this->isConflict =
true;
2347 } elseif ( $this->isConflict ) {
2351 $this->isConflict =
false;
2352 wfDebug( __METHOD__ .
": Suppressing edit conflict, successful merge." );
2354 $this->section =
'';
2355 $this->textbox1 = ContentHandler::getContentText(
$content );
2356 wfDebug( __METHOD__ .
": Keeping edit conflict, failed merge." );
2360 if ( $this->isConflict ) {
2361 $status->setResult(
false, self::AS_CONFLICT_DETECTED );
2369 if ( $this->section ==
'new' ) {
2371 if ( !$this->allowBlankSummary && trim( $this->summary ) ==
'' ) {
2372 $this->missingSummary =
true;
2373 $status->fatal(
'missingsummary' );
2379 if ( $this->textbox1 ==
'' ) {
2380 $this->missingComment =
true;
2381 $status->fatal(
'missingcommenttext' );
2385 } elseif ( !$this->allowBlankSummary
2386 && !
$content->equals( $this->getOriginalContent( $user ) )
2388 && md5( $this->summary ) == $this->autoSumm
2390 $this->missingSummary =
true;
2391 $status->fatal(
'missingsummary' );
2397 $sectionanchor =
'';
2398 if ( $this->section ==
'new' ) {
2400 } elseif ( $this->section !=
'' ) {
2401 # Try to get a section anchor from the section source, redirect
2402 # to edited section if header found.
2403 # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2404 # for duplicate heading checking and maybe parsing.
2405 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
2406 # We can't deal with anchors, includes, html etc in the header for now,
2407 # headline would need to be parsed to improve this.
2408 if ( $hasmatch && strlen(
$matches[2] ) > 0 ) {
2412 $result[
'sectionanchor'] = $sectionanchor;
2419 $this->section =
'';
2424 if ( !$this->allowSelfRedirect
2426 &&
$content->getRedirectTarget()->equals( $this->getTitle() )
2430 if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2431 $this->selfRedirect =
true;
2432 $status->fatal(
'selfredirect' );
2440 if ( $this->contentLength > $maxArticleSize * 1024 ) {
2441 $this->tooBig =
true;
2442 $status->setResult(
false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2448 ( ( $this->minoredit && !$this->isNew ) ?
EDIT_MINOR : 0 ) |
2451 $doEditStatus = $this->page->doEditContent(
2455 $this->undoAfter ?:
false,
2462 if ( !$doEditStatus->isOK() ) {
2466 $errors = $doEditStatus->getErrorsArray();
2467 if ( in_array( $errors[0][0],
2468 [
'edit-gone-missing',
'edit-conflict',
'edit-already-exists' ] )
2470 $this->isConflict =
true;
2474 return $doEditStatus;
2477 $result[
'nullEdit'] = $doEditStatus->hasMessage(
'edit-no-change' );
2478 if ( $result[
'nullEdit'] ) {
2480 $user->pingLimiter(
'linkpurge' );
2482 $result[
'redirect'] =
$content->isRedirect();
2487 if ( $changingContentModel ) {
2490 $new ?
false : $oldContentModel,
2506 $new = $oldModel ===
false;
2507 $log =
new ManualLogEntry(
'contentmodel', $new ?
'new' :
'change' );
2509 $log->setTarget( $this->mTitle );
2510 $log->setComment( $reason );
2511 $log->setParameters( [
2512 '4::oldmodel' => $oldModel,
2513 '5::newmodel' => $newModel
2515 $logid = $log->insert();
2516 $log->publish( $logid );
2523 $user = $this->context->getUser();
2524 if ( !$user->isLoggedIn() ) {
2539 if ( $this->watchlistExpiryEnabled ) {
2540 $purgeRate = $this->
getContext()->getConfig()->get(
'WatchlistPurgeRate' );
2541 $this->watchedItemStore->enqueueWatchlistExpiryJob( $purgeRate );
2560 $baseContent = $baseRevRecord ?
2561 $baseRevRecord->getContent( SlotRecord::MAIN ) :
2564 if ( $baseContent ===
null ) {
2569 $currentRevisionRecord = $this->revisionStore->getRevisionByTitle(
2572 RevisionStore::READ_LATEST
2574 $currentContent = $currentRevisionRecord
2575 ? $currentRevisionRecord->getContent( SlotRecord::MAIN )
2578 if ( $currentContent ===
null ) {
2582 $result = $this->contentHandlerFactory
2583 ->getContentHandler( $baseContent->getModel() )
2584 ->merge3( $baseContent, $editContent, $currentContent );
2587 $editContent = $result;
2589 $this->parentRevId = $currentRevisionRecord->getId();
2612 if ( $this->mBaseRevision ===
false ) {
2614 $this->mBaseRevision = $revRecord ?
new Revision( $revRecord ) :
null;
2627 if ( $this->mExpectedParentRevision ===
false ) {
2629 if ( $this->editRevId ) {
2630 $revRecord = $this->revisionStore->getRevisionById(
2632 RevisionStore::READ_LATEST
2635 $revRecord = $this->revisionStore->getRevisionByTimestamp(
2638 RevisionStore::READ_LATEST
2641 $this->mExpectedParentRevision = $revRecord;
2657 return MediaWikiServices::getInstance()->getSpamChecker()->checkContent( $text );
2671 return MediaWikiServices::getInstance()->getSpamChecker()->checkSummary( $text );
2675 $out = $this->context->getOutput();
2677 $out->addModules(
'mediawiki.action.edit' );
2678 $out->addModuleStyles(
'mediawiki.action.edit.styles' );
2679 $out->addModuleStyles(
'mediawiki.editfont.styles' );
2681 $user = $this->context->getUser();
2683 if ( $user->getOption(
'uselivepreview' ) ) {
2684 $out->addModules(
'mediawiki.action.edit.preview' );
2687 if ( $user->getOption(
'useeditwarning' ) ) {
2688 $out->addModules(
'mediawiki.action.edit.editWarning' );
2691 if ( $this->watchlistExpiryEnabled && $user->isRegistered() ) {
2692 $out->addModules(
'mediawiki.action.edit.watchlistExpiry' );
2695 # Enabled article-related sidebar, toplinks, etc.
2696 $out->setArticleRelated(
true );
2699 if ( $this->isConflict ) {
2700 $msg =
'editconflict';
2701 } elseif ( $contextTitle->exists() && $this->section !=
'' ) {
2702 $msg = $this->section ==
'new' ?
'editingcomment' :
'editingsection';
2704 $msg = $contextTitle->exists()
2706 && $contextTitle->getDefaultMessageText() !== false
2712 # Use the title defined by DISPLAYTITLE magic word when present
2713 # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2714 # setPageTitle() treats the input as wikitext, which should be safe in either case.
2715 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() :
false;
2716 if ( $displayTitle ===
false ) {
2717 $displayTitle = $contextTitle->getPrefixedText();
2719 $out->setDisplayTitle( $displayTitle );
2721 $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2723 $config = $this->context->getConfig();
2725 # Transmit the name of the message to JavaScript for live preview
2726 # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2727 $out->addJsConfigVars( [
2728 'wgEditMessage' => $msg,
2729 'wgAjaxEditStash' => $config->get(
'AjaxEditStash' ),
2734 $out->addJsConfigVars(
2735 'wgEditSubmitButtonLabelPublish',
2736 $config->get(
'EditSubmitButtonLabelPublish' )
2744 if ( $this->suppressIntro ) {
2748 $out = $this->context->getOutput();
2749 $namespace = $this->mTitle->getNamespace();
2752 # Show a warning if editing an interface message
2753 $out->wrapWikiMsg(
"<div class='mw-editinginterface'>\n$1\n</div>",
'editinginterface' );
2754 # If this is a default message (but not css, json, or js),
2755 # show a hint that it is translatable on translatewiki.net
2761 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2762 if ( $defaultMessageText !==
false ) {
2763 $out->wrapWikiMsg(
"<div class='mw-translateinterface'>\n$1\n</div>",
2764 'translateinterface' );
2767 } elseif ( $namespace ==
NS_FILE ) {
2768 # Show a hint to shared repo
2769 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $this->mTitle );
2771 $descUrl =
$file->getDescriptionUrl();
2772 # there must be a description url to show a hint to shared repo
2774 if ( !$this->mTitle->exists() ) {
2775 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2776 'sharedupload-desc-create',
$file->getRepo()->getDisplayName(), $descUrl
2779 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2780 'sharedupload-desc-edit',
$file->getRepo()->getDisplayName(), $descUrl
2787 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2788 # Show log extract when the user is currently blocked
2790 $username = explode(
'/', $this->mTitle->getText(), 2 )[0];
2793 $block = DatabaseBlock::newFromTarget( $user, $user );
2795 $userExists = ( $user && $user->isLoggedIn() );
2796 if ( $userExists && $user->isHidden() &&
2797 !$this->permManager->userHasRight( $this->context->getUser(),
'hideuser' )
2801 $userExists =
false;
2804 if ( !$userExists && !$ip ) { #
User does not exist
2805 $out->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2809 $block->getType() != DatabaseBlock::TYPE_AUTO &&
2810 ( $block->isSitewide() || $user->isBlockedFrom( $this->mTitle ) )
2814 LogEventsList::showLogExtract(
2817 MediaWikiServices::getInstance()->getNamespaceInfo()->
2818 getCanonicalName(
NS_USER ) .
':' . $block->getTarget(),
2822 'showIfEmpty' =>
false,
2824 'blocked-notice-logextract',
2825 $user->getName() # Support GENDER in notice
2831 # Try to add a custom edit intro, or use the standard one if this is not possible.
2833 $helpLink =
wfExpandUrl( Skin::makeInternalOrExternalUrl(
2834 $this->context->msg(
'helppage' )->inContentLanguage()->text()
2836 if ( $this->context->getUser()->isLoggedIn() ) {
2839 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2848 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2850 'newarticletextanon',
2856 # Give a notice if the user is editing a deleted/moved page...
2857 if ( !$this->mTitle->exists() ) {
2860 LogEventsList::showLogExtract( $out, [
'delete',
'move' ], $this->mTitle,
2864 'conds' => [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ],
2865 'showIfEmpty' =>
false,
2866 'msgKey' => [
'recreate-moveddeleted-warn' ]
2878 if ( $this->editintro ) {
2879 $title = Title::newFromText( $this->editintro );
2882 $this->context->getOutput()->addWikiTextAsContent(
2883 '<div class="mw-editintro">{{:' .
$title->getFullText() .
'}}</div>',
2920 return $content->serialize( $this->contentFormat );
2940 if ( $text ===
false || $text ===
null ) {
2945 $this->contentModel, $this->contentFormat );
2963 # need to parse the preview early so that we know which templates are used,
2964 # otherwise users with "show preview after edit box" will get a blank list
2965 # we parse this near the beginning so that setHeaders can do the title
2966 # setting work instead of leaving it in getPreviewText
2967 $previewOutput =
'';
2968 if ( $this->formtype ==
'preview' ) {
2972 $out = $this->context->getOutput();
2974 $this->getHookRunner()->onEditPage__showEditForm_initial( $this, $out );
2981 if ( !$this->isConflict &&
2982 $this->section !=
'' &&
2987 $out->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
2993 $out->addHTML( $this->editFormPageTop );
2995 $user = $this->context->getUser();
2996 if ( $user->getOption(
'previewontop' ) ) {
3000 $out->addHTML( $this->editFormTextTop );
3003 $out->wrapWikiMsg(
"<div class='error mw-deleted-while-editing'>\n$1\n</div>",
3004 'deletedwhileediting' );
3009 $out->addHTML( Html::openElement(
3012 'class' =>
'mw-editform',
3013 'id' => self::EDITFORM_ID,
3014 'name' => self::EDITFORM_ID,
3017 'enctype' =>
'multipart/form-data'
3021 if ( is_callable( $formCallback ) ) {
3022 wfWarn(
'The $formCallback parameter to ' . __METHOD__ .
'is deprecated' );
3023 call_user_func_array( $formCallback, [ &$out ] );
3027 $out->addHTML( Html::hidden(
'wpUnicodeCheck', self::UNICODE_CHECK ) );
3031 Xml::openElement(
'div', [
'id' =>
'antispam-container',
'style' =>
'display: none;' ] )
3034 [
'for' =>
'wpAntispam' ],
3035 $this->context->msg(
'simpleantispam-label' )->parse()
3041 'name' =>
'wpAntispam',
3042 'id' =>
'wpAntispam',
3046 . Xml::closeElement(
'div' )
3049 $this->getHookRunner()->onEditPage__showEditForm_fields( $this, $out );
3055 $username = $this->lastDelete->user_name;
3056 $comment = CommentStore::getStore()
3057 ->getComment(
'log_comment', $this->lastDelete )->text;
3061 $key = $comment ===
''
3062 ?
'confirmrecreate-noreason'
3063 :
'confirmrecreate';
3065 '<div class="mw-confirm-recreate">' .
3066 $this->context->msg( $key, $username,
"<nowiki>$comment</nowiki>" )->parse() .
3067 Xml::checkLabel( $this->context->msg(
'recreate' )->text(),
'wpRecreate',
'wpRecreate',
false,
3074 # When the summary is hidden, also hide them on preview/show changes
3075 if ( $this->nosummary ) {
3076 $out->addHTML( Html::hidden(
'nosummary',
true ) );
3079 # If a blank edit summary was previously provided, and the appropriate
3080 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
3081 # user being bounced back more than once in the event that a summary
3084 # For a bit more sophisticated detection of blank summaries, hash the
3085 # automatic one and pass that in the hidden field wpAutoSummary.
3086 if ( $this->missingSummary || ( $this->section ==
'new' && $this->nosummary ) ) {
3087 $out->addHTML( Html::hidden(
'wpIgnoreBlankSummary',
true ) );
3090 if ( $this->undidRev ) {
3091 $out->addHTML( Html::hidden(
'wpUndidRevision', $this->undidRev ) );
3093 if ( $this->undoAfter ) {
3094 $out->addHTML( Html::hidden(
'wpUndoAfter', $this->undoAfter ) );
3097 if ( $this->selfRedirect ) {
3098 $out->addHTML( Html::hidden(
'wpIgnoreSelfRedirect',
true ) );
3101 if ( $this->hasPresetSummary ) {
3105 $this->autoSumm = md5(
'' );
3108 $autosumm = $this->autoSumm !==
'' ? $this->autoSumm : md5( $this->summary );
3109 $out->addHTML( Html::hidden(
'wpAutoSummary', $autosumm ) );
3111 $out->addHTML( Html::hidden(
'oldid', $this->oldid ) );
3112 $out->addHTML( Html::hidden(
'parentRevId', $this->
getParentRevId() ) );
3114 $out->addHTML( Html::hidden(
'format', $this->contentFormat ) );
3115 $out->addHTML( Html::hidden(
'model', $this->contentModel ) );
3119 if ( $this->section ==
'new' ) {
3124 $out->addHTML( $this->editFormTextBeforeContent );
3125 if ( $this->isConflict ) {
3140 if ( !$this->mTitle->isUserConfigPage() ) {
3141 $out->addHTML( self::getEditToolbar() );
3144 if ( $this->blankArticle ) {
3145 $out->addHTML( Html::hidden(
'wpIgnoreBlankArticle',
true ) );
3148 if ( $this->isConflict ) {
3153 $conflictTextBoxAttribs = [];
3155 $conflictTextBoxAttribs[
'style'] =
'display:none;';
3156 } elseif ( $this->isOldRev ) {
3157 $conflictTextBoxAttribs[
'class'] =
'mw-textarea-oldrev';
3166 $out->addHTML( $this->editFormTextAfterContent );
3176 $out->addHTML( $this->editFormTextAfterTools .
"\n" );
3180 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'hiddencats' ],
3183 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'limitreport' ],
3184 self::getPreviewLimitReport( $this->mParserOutput ) ) );
3186 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
3188 if ( $this->isConflict ) {
3193 $msg = $this->context->msg(
3194 'content-failed-to-parse',
3195 $this->contentModel,
3196 $this->contentFormat,
3199 $out->wrapWikiTextAsInterface(
'error', $msg->plain() );
3204 if ( $this->isConflict ) {
3206 } elseif ( $this->preview ) {
3208 } elseif ( $this->diff ) {
3213 $out->addHTML( Html::hidden(
'mode', $mode, [
'id' =>
'mw-edit-mode' ] ) );
3217 $out->addHTML( Html::hidden(
'wpUltimateParam',
true ) );
3218 $out->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
3220 if ( !$user->getOption(
'previewontop' ) ) {
3234 $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
3239 if ( $this->preview ) {
3241 } elseif ( $this->section !=
'' ) {
3245 return Html::rawElement(
'div', [
'class' =>
'templatesUsed' ],
3246 $templateListFormatter->format( $templates,
$type )
3257 preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches );
3259 return MediaWikiServices::getInstance()->getParser()
3260 ->stripSectionName( trim(
$matches[2] ) );
3267 $out = $this->context->getOutput();
3268 $user = $this->context->getUser();
3269 if ( $this->isConflict ) {
3271 $this->editRevId = $this->page->getLatest();
3273 if ( $this->section !=
'' && $this->section !=
'new' && !$this->summary &&
3274 !$this->preview && !$this->diff
3277 if ( $sectionTitle !==
false ) {
3278 $this->summary =
"/* $sectionTitle */ ";
3284 if ( $this->missingComment ) {
3285 $out->wrapWikiMsg(
"<div id='mw-missingcommenttext'>\n$1\n</div>",
'missingcommenttext' );
3288 if ( $this->missingSummary && $this->section !=
'new' ) {
3290 "<div id='mw-missingsummary'>\n$1\n</div>",
3291 [
'missingsummary', $buttonLabel ]
3295 if ( $this->missingSummary && $this->section ==
'new' ) {
3297 "<div id='mw-missingcommentheader'>\n$1\n</div>",
3298 [
'missingcommentheader', $buttonLabel ]
3302 if ( $this->blankArticle ) {
3304 "<div id='mw-blankarticle'>\n$1\n</div>",
3305 [
'blankarticle', $buttonLabel ]
3309 if ( $this->selfRedirect ) {
3311 "<div id='mw-selfredirect'>\n$1\n</div>",
3312 [
'selfredirect', $buttonLabel ]
3316 if ( $this->hookError !==
'' ) {
3317 $out->addWikiTextAsInterface( $this->hookError );
3320 if ( $this->section !=
'new' ) {
3321 $revRecord = $this->mArticle->fetchRevisionRecord();
3325 if ( !RevisionRecord::userCanBitfield(
3326 $revRecord->getVisibility(),
3327 RevisionRecord::DELETED_TEXT,
3331 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3332 'rev-deleted-text-permission'
3334 } elseif ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
3336 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3337 'rev-deleted-text-view'
3341 if ( !$revRecord->isCurrent() ) {
3342 $this->mArticle->setOldSubtitle( $revRecord->getId() );
3344 Html::warningBox(
"\n$1\n" ),
3347 $this->isOldRev =
true;
3349 } elseif ( $this->mTitle->exists() ) {
3352 $out->wrapWikiMsg(
"<div class='errorbox'>\n$1\n</div>\n",
3353 [
'missing-revision', $this->oldid ] );
3360 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3363 } elseif ( $user->isAnon() ) {
3364 if ( $this->formtype !=
'preview' ) {
3365 $returntoquery = array_diff_key(
3366 $this->context->getRequest()->getValues(),
3367 [
'title' =>
true,
'returnto' =>
true,
'returntoquery' =>
true ]
3370 "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3371 [
'anoneditwarning',
3374 'returnto' => $this->
getTitle()->getPrefixedDBkey(),
3379 'returnto' => $this->
getTitle()->getPrefixedDBkey(),
3385 $out->wrapWikiMsg(
"<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3386 'anonpreviewwarning'
3389 } elseif ( $this->mTitle->isUserConfigPage() ) {
3390 # Check the skin exists
3393 "<div class='error' id='mw-userinvalidconfigtitle'>\n$1\n</div>",
3394 [
'userinvalidconfigtitle', $this->mTitle->getSkinFromConfigSubpage() ]
3397 if ( $this->
getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3398 $isUserCssConfig = $this->mTitle->isUserCssConfigPage();
3399 $isUserJsonConfig = $this->mTitle->isUserJsonConfigPage();
3400 $isUserJsConfig = $this->mTitle->isUserJsConfigPage();
3402 $warning = $isUserCssConfig
3404 : ( $isUserJsonConfig ?
'userjsonispublic' :
'userjsispublic' );
3406 $out->wrapWikiMsg(
'<div class="mw-userconfigpublic">$1</div>', $warning );
3408 if ( $isUserJsConfig ) {
3409 $out->wrapWikiMsg(
'<div class="mw-userconfigdangerous">$1</div>',
'userjsdangerous' );
3412 if ( $this->formtype !==
'preview' ) {
3413 $config = $this->context->getConfig();
3414 if ( $isUserCssConfig && $config->get(
'AllowUserCss' ) ) {
3416 "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3417 [
'usercssyoucanpreview' ]
3419 } elseif ( $isUserJsonConfig ) {
3421 "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>",
3422 [
'userjsonyoucanpreview' ]
3424 } elseif ( $isUserJsConfig && $config->get(
'AllowUserJs' ) ) {
3426 "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3427 [
'userjsyoucanpreview' ]
3438 # Add header copyright warning
3453 return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3454 'id' =>
'wpSummary',
3455 'name' =>
'wpSummary',
3456 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
3459 'spellcheck' =>
'true',
3473 $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3482 $inputAttrs[
'inputId'] = $inputAttrs[
'id'];
3483 $inputAttrs[
'id'] =
'wpSummaryWidget';
3485 return new OOUI\FieldLayout(
3486 new OOUI\TextInputWidget( [
3488 'infusable' =>
true,
3491 'label' =>
new OOUI\HtmlSnippet( $labelText ),
3493 'id' =>
'wpSummaryLabel',
3494 'classes' => [ $this->missingSummary ?
'mw-summarymissed' :
'mw-summary' ],
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();
3520 [
'class' => $summaryClass ]
3534 if ( !
$summary || ( !$this->preview && !$this->diff ) ) {
3538 if ( $isSubjectPreview ) {
3539 $summary = $this->context->msg(
'newsectionsummary' )
3540 ->rawParams( MediaWikiServices::getInstance()->getParser()
3542 ->inContentLanguage()->text();
3545 $message = $isSubjectPreview ?
'subject-preview' :
'summary-preview';
3547 $summary = $this->context->msg( $message )->parse()
3549 return Xml::tags(
'div', [
'class' =>
'mw-summary-preview' ],
$summary );
3553 $out = $this->context->getOutput();
3554 $out->addHTML( Html::hidden(
'wpSection', $this->section ) );
3555 $out->addHTML( Html::hidden(
'wpStarttime', $this->starttime ) );
3556 $out->addHTML( Html::hidden(
'wpEdittime', $this->edittime ) );
3557 $out->addHTML( Html::hidden(
'editRevId', $this->editRevId ) );
3558 $out->addHTML( Html::hidden(
'wpScrolltop', $this->scrolltop, [
'id' =>
'wpScrolltop' ] ) );
3574 $this->context->getOutput()->addHTML(
3576 Html::hidden(
"wpEditToken", $this->context->getUser()->getEditToken() ) .
3601 protected function showTextbox1( $customAttribs =
null, $textoverride =
null ) {
3603 $attribs = [
'style' =>
'display:none;' ];
3606 $classes = $builder->getTextboxProtectionCSSClasses( $this->
getTitle() );
3608 # Is an old revision being edited?
3609 if ( $this->isOldRev ) {
3610 $classes[] =
'mw-textarea-oldrev';
3614 'aria-label' => $this->context->msg(
'edit-textarea-aria-label' )->text(),
3618 if ( is_array( $customAttribs ) ) {
3619 $attribs += $customAttribs;
3622 $attribs = $builder->mergeClassesIntoAttributes( $classes, $attribs );
3626 $textoverride ?? $this->textbox1,
3633 $this->
showTextbox( $this->textbox2,
'wpTextbox2', [
'tabindex' => 6,
'readonly' ] );
3638 $attribs = $builder->buildTextboxAttribs(
3641 $this->context->getUser(),
3645 $this->context->getOutput()->addHTML(
3646 Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
3653 $classes[] =
'ontop';
3656 $attribs = [
'id' =>
'wikiPreview',
'class' => implode(
' ', $classes ) ];
3658 if ( $this->formtype !=
'preview' ) {
3659 $attribs[
'style'] =
'display: none;';
3662 $out = $this->context->getOutput();
3663 $out->addHTML( Xml::openElement(
'div', $attribs ) );
3665 if ( $this->formtype ==
'preview' ) {
3669 $pageViewLang = $this->mTitle->getPageViewLanguage();
3670 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3671 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3672 $out->addHTML( Html::rawElement(
'div', $attribs ) );
3675 $out->addHTML(
'</div>' );
3677 if ( $this->formtype ==
'diff' ) {
3681 $msg = $this->context->msg(
3682 'content-failed-to-parse',
3683 $this->contentModel,
3684 $this->contentFormat,
3687 $out->wrapWikiTextAsInterface(
'error', $msg->plain() );
3700 $this->mArticle->openShowCategory();
3702 # This hook seems slightly odd here, but makes things more
3703 # consistent for extensions.
3704 $out = $this->context->getOutput();
3705 $this->getHookRunner()->onOutputPageBeforeHTML( $out, $text );
3706 $out->addHTML( $text );
3708 $this->mArticle->closeShowCategory();
3720 $oldtitlemsg =
'currentrev';
3721 # if message does not exist, show diff against the preloaded default
3722 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3723 $oldtext = $this->mTitle->getDefaultMessageText();
3724 if ( $oldtext !==
false ) {
3725 $oldtitlemsg =
'defaultmessagetext';
3735 if ( $this->editRevId !==
null ) {
3736 $newContent = $this->page->replaceSectionAtRev(
3737 $this->section, $textboxContent, $this->summary, $this->editRevId
3740 $newContent = $this->page->replaceSectionContent(
3741 $this->section, $textboxContent, $this->summary, $this->edittime
3745 if ( $newContent ) {
3746 $this->getHookRunner()->onEditPageGetDiffContent( $this, $newContent );
3748 $user = $this->context->getUser();
3749 $popts = ParserOptions::newFromUserAndLang( $user,
3750 MediaWikiServices::getInstance()->getContentLanguage() );
3751 $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3754 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3755 $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3756 $newtitle = $this->context->msg(
'yourtext' )->parse();
3758 if ( !$oldContent ) {
3759 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3762 if ( !$newContent ) {
3763 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3766 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3767 $de->setContent( $oldContent, $newContent );
3769 $difftext = $de->getDiff( $oldtitle, $newtitle );
3770 $de->showDiffStyle();
3775 $this->context->getOutput()->addHTML(
'<div id="wikiDiff">' . $difftext .
'</div>' );
3782 $msg =
'editpage-head-copy-warn';
3783 if ( !$this->context->msg( $msg )->isDisabled() ) {
3784 $this->context->getOutput()->wrapWikiMsg(
"<div class='editpage-head-copywarn'>\n$1\n</div>",
3785 'editpage-head-copy-warn' );
3798 $msg =
'editpage-tos-summary';
3799 $this->getHookRunner()->onEditPageTosSummary( $this->mTitle, $msg );
3800 if ( !$this->context->msg( $msg )->isDisabled() ) {
3801 $out = $this->context->getOutput();
3802 $out->addHTML(
'<div class="mw-tos-summary">' );
3803 $out->addWikiMsg( $msg );
3804 $out->addHTML(
'</div>' );
3813 $this->context->getOutput()->addHTML(
'<div class="mw-editTools">' .
3814 $this->context->msg(
'edittools' )->inContentLanguage()->parse() .
3839 $copywarnMsg = [
'copyrightwarning',
3840 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]',
3843 $copywarnMsg = [
'copyrightwarning2',
3844 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]' ];
3847 Hooks::runner()->onEditPageCopyrightWarning(
$title, $copywarnMsg );
3851 $msg->inLanguage( $langcode );
3853 return "<div id=\"editpage-copywarn\">\n" .
3854 $msg->$format() .
"\n</div>";
3867 if ( !$output || !$output->getLimitReportData() ) {
3871 $limitReport = Html::rawElement(
'div', [
'class' =>
'mw-limitReportExplanation' ],
3872 wfMessage(
'limitreport-title' )->parseAsBlock()
3876 $limitReport .= Html::openElement(
'div', [
'class' =>
'preview-limit-report-wrapper' ] );
3878 $limitReport .= Html::openElement(
'table', [
3879 'class' =>
'preview-limit-report wikitable'
3881 Html::openElement(
'tbody' );
3883 foreach ( $output->getLimitReportData() as $key => $value ) {
3884 if ( Hooks::runner()->onParserLimitReportFormat( $key, $value, $limitReport,
true,
true ) ) {
3886 $valueMsg =
wfMessage( [
"$key-value-html",
"$key-value" ] );
3887 if ( !$valueMsg->exists() ) {
3890 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3891 $limitReport .= Html::openElement(
'tr' ) .
3892 Html::rawElement(
'th',
null, $keyMsg->parse() ) .
3893 Html::rawElement(
'td',
null,
3894 $wgLang->formatNum( $valueMsg->params( $value )->parse() )
3896 Html::closeElement(
'tr' );
3901 $limitReport .= Html::closeElement(
'tbody' ) .
3902 Html::closeElement(
'table' ) .
3903 Html::closeElement(
'div' );
3905 return $limitReport;
3909 $out = $this->context->getOutput();
3910 $out->addHTML(
"<div class='editOptions'>\n" );
3912 if ( $this->section !=
'new' ) {
3919 [
'minor' => $this->minoredit,
'watch' => $this->watchthis ]
3921 $checkboxesHTML =
new OOUI\HorizontalLayout( [
'items' => $checkboxes ] );
3923 $out->addHTML(
"<div class='editCheckboxes'>" . $checkboxesHTML .
"</div>\n" );
3926 $out->addWikiTextAsInterface( $this->
getCopywarn() );
3927 $out->addHTML( $this->editFormTextAfterWarn );
3929 $out->addHTML(
"<div class='editButtons'>\n" );
3930 $out->addHTML( implode(
"\n", $this->
getEditButtons( $tabindex ) ) .
"\n" );
3934 $message = $this->context->msg(
'edithelppage' )->inContentLanguage()->text();
3935 $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3938 $this->context->msg(
'edithelp' )->text(),
3939 [
'target' =>
'helpwindow',
'href' => $edithelpurl ],
3942 $this->context->msg(
'word-separator' )->escaped() .
3943 $this->context->msg(
'newwindow' )->parse();
3945 $out->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3946 $out->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3947 $out->addHTML(
"</div><!-- editButtons -->\n" );
3949 $this->getHookRunner()->onEditPage__showStandardInputs_options( $this, $out, $tabindex );
3951 $out->addHTML(
"</div><!-- editOptions -->\n" );
3959 $out = $this->context->getOutput();
3962 if ( $this->getHookRunner()->onEditPageBeforeConflictDiff( $editPage, $out ) ) {
3979 if ( !$this->isConflict && $this->oldid > 0 ) {
3982 $cancelParams[
'redirect'] =
'no';
3985 return new OOUI\ButtonWidget( [
3986 'id' =>
'mw-editform-cancel',
3987 'tabIndex' => $tabindex,
3989 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
'cancel' )->parse() ),
3991 'infusable' =>
true,
3992 'flags' =>
'destructive',
4006 return $title->getLocalURL( [
'action' => $this->action ] );
4017 if ( $this->deletedSinceEdit !==
null ) {
4021 $this->deletedSinceEdit =
false;
4023 if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
4025 if ( $this->lastDelete ) {
4026 $deleteTime =
wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
4027 if ( $deleteTime > $this->starttime ) {
4028 $this->deletedSinceEdit =
true;
4043 $commentQuery = CommentStore::getStore()->getJoin(
'log_comment' );
4044 $actorQuery = ActorMigration::newMigration()->getJoin(
'log_user' );
4045 $data =
$dbr->selectRow(
4046 array_merge( [
'logging' ], $commentQuery[
'tables'], $actorQuery[
'tables'], [
'user' ] ),
4056 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
4058 'log_namespace' => $this->mTitle->getNamespace(),
4059 'log_title' => $this->mTitle->getDBkey(),
4060 'log_type' =>
'delete',
4061 'log_action' =>
'delete',
4064 [
'LIMIT' => 1,
'ORDER BY' =>
'log_timestamp DESC' ],
4066 'user' => [
'JOIN',
'user_id=' . $actorQuery[
'fields'][
'log_user'] ],
4067 ] + $commentQuery[
'joins'] + $actorQuery[
'joins']
4070 if ( is_object( $data ) ) {
4072 $data->user_name = $this->context->msg(
'rev-deleted-user' )->escaped();
4076 $data->log_comment_text = $this->context->msg(
'rev-deleted-comment' )->escaped();
4077 $data->log_comment_data =
null;
4090 $out = $this->context->getOutput();
4091 $config = $this->context->getConfig();
4093 if ( $config->get(
'RawHtml' ) && !$this->mTokenOk ) {
4097 if ( $this->textbox1 !==
'' ) {
4101 $parsedNote = Html::rawElement(
'div', [
'class' =>
'previewnote' ],
4102 $out->parseAsInterface(
4103 $this->context->msg(
'session_fail_preview_html' )->plain()
4116 if ( !$this->getHookRunner()->onAlternateEditPreview(
4117 $this,
$content, $previewHTML, $this->mParserOutput )
4119 return $previewHTML;
4122 # provide a anchor link to the editform
4123 $continueEditing =
'<span class="mw-continue-editing">' .
4124 '[[#' . self::EDITFORM_ID .
'|' .
4125 $this->context->getLanguage()->getArrow() .
' ' .
4126 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
4127 if ( $this->mTriedSave && !$this->mTokenOk ) {
4128 if ( $this->mTokenOkExceptSuffix ) {
4129 $note = $this->context->msg(
'token_suffix_mismatch' )->plain();
4132 $note = $this->context->msg(
'session_fail_preview' )->plain();
4135 } elseif ( $this->incompleteForm ) {
4136 $note = $this->context->msg(
'edit_form_incomplete' )->plain();
4137 if ( $this->mTriedSave ) {
4141 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
4144 # don't parse non-wikitext pages, show message about preview
4145 if ( $this->mTitle->isUserConfigPage() || $this->mTitle->isSiteConfigPage() ) {
4146 if ( $this->mTitle->isUserConfigPage() ) {
4148 } elseif ( $this->mTitle->isSiteConfigPage() ) {
4156 if ( $level ===
'user' && !$config->get(
'AllowUserCss' ) ) {
4161 if ( $level ===
'user' ) {
4166 if ( $level ===
'user' && !$config->get(
'AllowUserJs' ) ) {
4173 # Used messages to make sure grep find them:
4174 # Messages: usercsspreview, userjsonpreview, userjspreview,
4175 # sitecsspreview, sitejsonpreview, sitejspreview
4176 if ( $level && $format ) {
4177 $note =
"<div id='mw-{$level}{$format}preview'>" .
4178 $this->context->msg(
"{$level}{$format}preview" )->plain() .
4179 ' ' . $continueEditing .
"</div>";
4183 # If we're adding a comment, we need to show the
4184 # summary as the headline
4185 if ( $this->section ===
"new" && $this->summary !==
"" ) {
4189 $this->getHookRunner()->onEditPageGetPreviewContent( $this,
$content );
4192 $parserOutput = $parserResult[
'parserOutput'];
4193 $previewHTML = $parserResult[
'html'];
4194 $this->mParserOutput = $parserOutput;
4195 $out->addParserOutputMetadata( $parserOutput );
4196 if ( $out->userCanPreview() ) {
4200 if ( count( $parserOutput->getWarnings() ) ) {
4201 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
4205 $m = $this->context->msg(
4206 'content-failed-to-parse',
4207 $this->contentModel,
4208 $this->contentFormat,
4211 $note .=
"\n\n" . $m->plain(); # gets parsed down below
4215 if ( $this->isConflict ) {
4216 $conflict = Html::rawElement(
4217 'div', [
'id' =>
'mw-previewconflict',
'class' =>
'warningbox' ],
4218 $this->context->msg(
'previewconflict' )->escaped()
4224 $previewhead = Html::rawElement(
4225 'div', [
'class' =>
'previewnote' ],
4227 'h2', [
'id' =>
'mw-previewheader' ],
4228 $this->context->msg(
'preview' )->escaped()
4230 Html::rawElement(
'div', [
'class' =>
'warningbox' ],
4231 $out->parseAsInterface( $note )
4235 $pageViewLang = $this->mTitle->getPageViewLanguage();
4236 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
4237 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
4238 $previewHTML = Html::rawElement(
'div', $attribs, $previewHTML );
4244 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
4245 $stats->increment(
'edit.failures.' . $failureType );
4253 $parserOptions = $this->page->makeParserOptions( $this->context );
4254 $parserOptions->setIsPreview(
true );
4255 $parserOptions->setIsSectionPreview( $this->section !==
null && $this->section !==
'' );
4256 $parserOptions->enableLimitReport();
4263 return $parserOptions;
4276 $user = $this->context->getUser();
4284 $pstContent =
$content->preSaveTransform( $this->mTitle, $user, $parserOptions );
4285 $scopedCallback = $parserOptions->setupFakeRevision( $this->mTitle, $pstContent, $user );
4286 $parserOutput = $pstContent->getParserOutput( $this->mTitle,
null, $parserOptions );
4287 ScopedCallback::consume( $scopedCallback );
4289 'parserOutput' => $parserOutput,
4290 'html' => $parserOutput->getText( [
4291 'enableSectionEditLinks' =>
false
4300 if ( $this->preview || $this->section !=
'' ) {
4302 if ( !isset( $this->mParserOutput ) ) {
4305 foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
4306 foreach ( array_keys( $template ) as $dbk ) {
4307 $templates[] = Title::makeTitle( $ns, $dbk );
4312 return $this->mTitle->getTemplateLinksFrom();
4322 $startingToolbar =
'<div id="toolbar"></div>';
4323 $toolbar = $startingToolbar;
4325 if ( !Hooks::runner()->onEditPageBeforeEditToolbar( $toolbar ) ) {
4329 return ( $toolbar === $startingToolbar ) ? null : $toolbar;
4360 $user = $this->context->getUser();
4362 if ( !$this->isNew && $this->permManager->userHasRight( $user,
'minoredit' ) ) {
4363 $checkboxes[
'wpMinoredit'] = [
4364 'id' =>
'wpMinoredit',
4365 'label-message' =>
'minoredit',
4367 'tooltip' =>
'minoredit',
4368 'label-id' =>
'mw-editpage-minoredit',
4369 'legacy-name' =>
'minor',
4370 'default' => $checked[
'minor'],
4374 if ( $user->isLoggedIn() ) {
4375 $checkboxes = array_merge(
4381 $this->getHookRunner()->onEditPageGetCheckboxesDefinition( $this, $checkboxes );
4396 'id' =>
'wpWatchthis',
4397 'label-message' =>
'watchthis',
4399 'tooltip' =>
'watch',
4400 'label-id' =>
'mw-editpage-watch',
4401 'legacy-name' =>
'watch',
4402 'default' => $watch,
4405 if ( $this->watchlistExpiryEnabled ) {
4411 $expiryFromRequest = $this->
getContext()->getRequest()->getText(
'wpWatchlistExpiry' );
4412 if ( $this->preview && in_array( $expiryFromRequest, $expiryOptions[
'options'] ) ) {
4413 $expiryOptions[
'default'] = $expiryFromRequest;
4417 foreach ( $expiryOptions[
'options'] as $label => $value ) {
4418 $options[] = [
'data' => $value,
'label' => $label ];
4420 $fieldDefs[
'wpWatchlistExpiry'] = [
4421 'id' =>
'wpWatchlistExpiry',
4422 'label-message' =>
'confirm-watch-label',
4424 'tooltip' =>
'watchlist-expiry',
4425 'label-id' =>
'mw-editpage-watchlist-expiry',
4426 'default' => $expiryOptions[
'default'],
4427 'value-attr' =>
'value',
4428 'class' => DropdownInputWidget::class,
4429 'options' => $options,
4430 'invisibleLabel' =>
true,
4451 foreach ( $checkboxesDef as $name => $options ) {
4452 $legacyName = $options[
'legacy-name'] ?? $name;
4456 if ( isset( $options[
'tooltip'] ) ) {
4457 $accesskey = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4460 if ( isset( $options[
'title-message'] ) ) {
4461 $title = $this->context->msg( $options[
'title-message'] )->text();
4465 $className = $options[
'class'] ?? CheckboxInputWidget::class;
4466 $valueAttr = $options[
'value-attr'] ??
'selected';
4467 $checkboxes[ $legacyName ] =
new FieldLayout(
4469 'tabIndex' => ++$tabindex,
4470 'accessKey' => $accesskey,
4471 'id' => $options[
'id'] .
'Widget',
4472 'inputId' => $options[
'id'],
4474 $valueAttr => $options[
'default'],
4475 'infusable' =>
true,
4476 'options' => $options[
'options'] ??
null,
4479 'align' =>
'inline',
4480 'label' =>
new OOUI\HtmlSnippet( $this->context->msg( $options[
'label-message'] )->parse() ),
4482 'id' => $options[
'label-id'] ??
null,
4483 'invisibleLabel' => $options[
'invisibleLabel'] ??
null,
4499 $this->context->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4502 $newPage = !$this->mTitle->exists();
4504 if ( $labelAsPublish ) {
4505 $buttonLabelKey = $newPage ?
'publishpage' :
'publishchanges';
4507 $buttonLabelKey = $newPage ?
'savearticle' :
'savechanges';
4510 return $buttonLabelKey;
4525 $this->context->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4528 $buttonTooltip = $labelAsPublish ?
'publish' :
'save';
4530 $buttons[
'save'] =
new OOUI\ButtonInputWidget( [
4532 'tabIndex' => ++$tabindex,
4533 'id' =>
'wpSaveWidget',
4534 'inputId' =>
'wpSave',
4536 'useInputTag' =>
true,
4537 'flags' => [
'progressive',
'primary' ],
4538 'label' => $buttonLabel,
4539 'infusable' =>
true,
4547 $buttons[
'preview'] =
new OOUI\ButtonInputWidget( [
4548 'name' =>
'wpPreview',
4549 'tabIndex' => ++$tabindex,
4550 'id' =>
'wpPreviewWidget',
4551 'inputId' =>
'wpPreview',
4553 'useInputTag' =>
true,
4554 'label' => $this->context->msg(
'showpreview' )->text(),
4555 'infusable' =>
true,
4563 $buttons[
'diff'] =
new OOUI\ButtonInputWidget( [
4565 'tabIndex' => ++$tabindex,
4566 'id' =>
'wpDiffWidget',
4567 'inputId' =>
'wpDiff',
4569 'useInputTag' =>
true,
4570 'label' => $this->context->msg(
'showdiff' )->text(),
4571 'infusable' =>
true,
4579 $this->getHookRunner()->onEditPageBeforeEditButtons( $this, $buttons, $tabindex );
4589 $out = $this->context->getOutput();
4590 $out->prepareErrorPage( $this->context->msg(
'nosuchsectiontitle' ) );
4592 $res = $this->context->msg(
'nosuchsectiontext', $this->section )->parseAsBlock();
4594 $this->getHookRunner()->onEditPageNoSuchSection( $this,
$res );
4595 $out->addHTML(
$res );
4597 $out->returnToMain(
false, $this->mTitle );
4608 if ( is_array( $match ) ) {
4609 $match = $this->context->getLanguage()->listToText( $match );
4611 $out = $this->context->getOutput();
4612 $out->prepareErrorPage( $this->context->msg(
'spamprotectiontitle' ) );
4614 $out->addHTML(
'<div id="spamprotected">' );
4615 $out->addWikiMsg(
'spamprotectiontext' );
4619 $out->addHTML(
'</div>' );
4621 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
4624 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
4627 $out->addReturnTo( $this->
getContextTitle(), [
'action' =>
'edit' ] );
4634 $out = $this->context->getOutput();
4635 $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4636 if ( count( $editNotices ) ) {
4637 $out->addHTML( implode(
"\n", $editNotices ) );
4639 $msg = $this->context->msg(
'editnotice-notext' );
4640 if ( !$msg->isDisabled() ) {
4642 '<div class="mw-editnotice-notext">'
4643 . $msg->parseAsBlock()
4654 if ( $this->mTitle->isTalkPage() ) {
4655 $this->context->getOutput()->addWikiMsg(
'talkpagetext' );
4663 if ( $this->contentLength ===
false ) {
4664 $this->contentLength = strlen( $this->textbox1 );
4667 $out = $this->context->getOutput();
4668 $lang = $this->context->getLanguage();
4669 $maxArticleSize = $this->context->getConfig()->get(
'MaxArticleSize' );
4670 if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4671 $out->wrapWikiMsg(
"<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4674 $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4675 $lang->formatNum( $maxArticleSize )
4678 } elseif ( !$this->context->msg(
'longpage-hint' )->isDisabled() ) {
4679 $out->wrapWikiMsg(
"<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4682 $lang->formatSize( strlen( $this->textbox1 ) ),
4683 strlen( $this->textbox1 )
4693 $out = $this->context->getOutput();
4694 if ( $this->mTitle->isProtected(
'edit' ) &&
4695 $this->permManager->getNamespaceRestrictionLevels(
4696 $this->getTitle()->getNamespace()
4699 # Is the title semi-protected?
4700 if ( $this->mTitle->isSemiProtected() ) {
4701 $noticeMsg =
'semiprotectedpagewarning';
4703 # Then it must be protected based on static groups (regular)
4704 $noticeMsg =
'protectedpagewarning';
4706 LogEventsList::showLogExtract( $out,
'protect', $this->mTitle,
'',
4707 [
'lim' => 1,
'msgKey' => [ $noticeMsg ] ] );
4709 if ( $this->mTitle->isCascadeProtected() ) {
4710 # Is this page under cascading protection from some source pages?
4712 list( $cascadeSources, ) = $this->mTitle->getCascadeProtectionSources();
4713 $notice =
"<div class='warningbox mw-cascadeprotectedwarning'>\n$1\n";
4714 $cascadeSourcesCount = count( $cascadeSources );
4715 if ( $cascadeSourcesCount > 0 ) {
4716 # Explain, and list the titles responsible
4717 foreach ( $cascadeSources as
$page ) {
4718 $notice .=
'* [[:' .
$page->getPrefixedText() .
"]]\n";
4721 $notice .=
'</div>';
4722 $out->wrapWikiMsg( $notice, [
'cascadeprotectedwarning', $cascadeSourcesCount ] );
4724 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions(
'create' ) ) {
4725 LogEventsList::showLogExtract( $out,
'protect', $this->mTitle,
'',
4727 'showIfEmpty' =>
false,
4728 'msgKey' => [
'titleprotectedwarning' ],
4729 'wrap' =>
"<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4752 $name, $customAttribs, $user, $this->mTitle
4777 $userAgent = $this->context->getRequest()->getHeader(
'User-Agent' );
4778 $parser = MediaWikiServices::getInstance()->getParser();
4779 if ( $userAgent && preg_match(
'/MSIE|Edge/', $userAgent ) ) {
4781 return $parser->guessLegacySectionNameFromWikiText( $text );
4784 $name = $parser->guessSectionNameFromWikiText( $text );
4787 return '#' . urlencode( mb_substr( $name, 1 ) );
4797 $this->editConflictHelperFactory = $factory;
4798 $this->editConflictHelper =
null;
4805 if ( !$this->editConflictHelper ) {
4806 $this->editConflictHelper = call_user_func(
4807 $this->editConflictHelperFactory,
4824 MediaWikiServices::getInstance()->getStatsdDataFactory(),
4826 MediaWikiServices::getInstance()->getContentHandlerFactory()
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
$wgRightsText
If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link.
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Class for viewing MediaWiki article and history.
getContext()
Gets the context this Article is executed in.
getTitle()
Get the title object of the article.
getPage()
Get the WikiPage object of this instance.
Special handling for category description pages, showing pages, subcategories and file that belong to...
An IContextSource implementation which will inherit context from another source but allow individual ...
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
getPreviewParserOptions()
Get parser options for a preview.
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
bool stdClass $lastDelete
showTextbox( $text, $name, $customAttribs=[])
attemptSave(&$resultDetails=false)
Attempt submission.
isSectionEditSupported()
Returns whether section editing is supported for the current page.
getCheckboxesWidget(&$tabindex, $checked)
Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and any ot...
updateWatchlist()
Register the change of watch status.
getOriginalContent(User $user)
Get the content of the wanted revision, without section extraction.
null string $contentFormat
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
bool $firsttime
True the first time the edit form is rendered, false after re-rendering with diff,...
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
showPreview( $text)
Append preview output to OutputPage.
getCheckboxesDefinition( $checked)
Return an array of checkbox definitions.
showEditTools()
Inserts optional text shown below edit and upload forms.
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
$editFormTextAfterContent
int $editRevId
Revision ID of the latest revision of the page when editing was initiated on the client.
$editFormTextBeforeContent
internalAttemptSave(&$result, $markAsBot=false)
Attempt submission (no UI)
__construct(Article $article)
Stable to call.
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
int $oldid
Revision ID the edit is based on, or 0 if it's the current revision.
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
addExplainConflictHeader(OutputPage $out)
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the necessary attributes for the input.
incrementEditFailureStats( $failureType)
setPostEditCookie( $statusValue)
Sets post-edit cookie indicating the user just saved a particular revision.
getCancelLink( $tabindex=0)
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
setEditConflictHelperFactory(callable $factory)
Set a factory function to create an EditConflictHelper.
string null $unicodeCheck
What the user submitted in the 'wpUnicodeCheck' field.
static getEditToolbar()
Allow extensions to provide a toolbar.
callable $editConflictHelperFactory
Factory function to create an edit conflict helper.
int $parentRevId
Revision ID the edit is based on, adjusted when an edit conflict is resolved.
isWrongCaseUserConfigPage()
Checks whether the user entered a skin name in uppercase, e.g.
initialiseForm()
Initialise form fields in the object Called on the first invocation, e.g.
static getPreviewLimitReport(ParserOutput $output=null)
Get the Limit report for page previews.
bool $hasPresetSummary
Has a summary been preset using GET parameter &summary= ?
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match.
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
static extractSectionTitle( $text)
Extract the section title from current section text, if any.
showTextbox1( $customAttribs=null, $textoverride=null)
Method to output wpTextbox1 The $textoverride method can be used by subclasses overriding showContent...
buildTextboxAttribs( $name, array $customAttribs, User $user)
showDiff()
Get a diff between the current contents of the edit box and the version of the page we're editing fro...
Revision bool null $mBaseRevision
A revision object corresponding to $this->editRevId.
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
incrementResolvedConflicts()
Log when a page was successfully saved after the edit conflict view.
showConflict()
Show an edit conflict.
getCheckboxesDefinitionForWatchlist( $watch)
Get the watchthis and watchlistExpiry form field definitions.
guessSectionName( $text)
Turns section name wikitext into anchors for use in HTTP redirects.
setPreloadedContent(Content $content)
Use this method before edit() to preload some content into the edit box.
static getCopyrightWarning( $title, $format='plain', $langcode=null)
Get the copyright warning, by default returns wikitext.
getParentRevId()
Get the edit's parent revision ID.
tokenOk(&$request)
Make sure the form isn't faking a user's credentials.
makeTemplatesOnThisPageList(array $templates)
Wrapper around TemplatesOnThisPageFormatter to make a "templates on this page" list.
ParserOutput $mParserOutput
previewOnOpen()
Should we show a preview when the edit form is first shown?
displayPreviewArea( $previewOutput, $isOnTop=false)
doPreviewParse(Content $content)
Parse the page for a preview.
RevisionStore $revisionStore
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing.
PermissionManager $permManager
getSubmitButtonLabel()
Get the message key of the label for the button to save the page.
getEditPermissionErrors( $rigor=PermissionManager::RIGOR_SECURE)
getSummaryInputWidget( $summary="", $labelText=null, $inputAttrs=null)
Builds a standard summary input with a label.
showStandardInputs(&$tabindex=2)
getCurrentContent()
Get the current content of the page.
getExpectedParentRevision()
Returns the RevisionRecord corresponding to the revision that was current at the time editing was ini...
string $textbox1
Page content input field.
addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason)
const EDITFORM_ID
HTML id and name for the beginning of the edit form.
string $editFormPageTop
Before even the preview.
getCopywarn()
Get the copyright warning.
const POST_EDIT_COOKIE_DURATION
Duration of PostEdit cookie, in seconds.
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
getContextTitle()
Get the context title object.
toEditText( $content)
Gets an editable textual representation of $content.
null Title $mContextTitle
RevisionRecord bool null $mExpectedParentRevision
A RevisionRecord corresponding to $this->editRevId or $this->edittime Replaced $mBaseRevision.
bool $isOldRev
Whether an old revision is edited.
bool $watchlistExpiryEnabled
Corresponds to $wgWatchlistExpiry.
string null $watchlistExpiry
The expiry time of the watch item, or null if it is not watched temporarily.
bool $mTokenOkExceptSuffix
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
getBaseRevision()
Returns the revision that was current at the time editing was initiated on the client,...
string $starttime
Timestamp from the first time the edit form was rendered.
bool $isNew
New page or new section.
showIntro()
Show all applicable editing introductions.
getContentObject( $def_content=null)
bool $isConflict
Whether an edit conflict needs to be resolved.
getLastDelete()
Get the last log record of this page being deleted, if ever.
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff and preview.
edit()
This is the function that gets called for "action=edit".
newTextConflictHelper( $submitButtonLabel)
bool $enableApiEditOverride
Set in ApiEditPage, based on ContentHandler::allowsDirectApiEditing.
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
addPageProtectionWarningHeaders()
addLongPageWarningHeader()
WatchedItemStoreInterface $watchedItemStore
addNewLineAtEnd( $wikitext)
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
showHeaderCopyrightWarning()
Show the header copyright warning.
getSummaryPreview( $isSubjectPreview, $summary="")
showSummaryInput( $isSubjectPreview, $summary="")
mergeChangesIntoContent(&$editContent)
Attempts to do 3-way merge of edit content with a base revision and current content,...
isPageExistingAndViewable( $title, User $user)
Verify if a given title exists and the given user is allowed to view it.
string $edittime
Timestamp of the latest revision of the page when editing was initiated on the client.
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
const UNICODE_CHECK
Used for Unicode support checks.
getPreviewText()
Get the rendered text for previewing.
formatStatusErrors(Status $status)
Wrap status errors in an errorbox for increased visibility.
IContentHandlerFactory $contentHandlerFactory
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
getPreloadedContent( $preload, $params=[])
Get the contents to be preloaded into the box, either set by an earlier setPreloadText() or by loadin...
TextConflictHelper null $editConflictHelper
showEditForm( $formCallback=null)
Send the edit form and related headers to OutputPage.
setContextTitle( $title)
Set the context Title object.
spamPageWithContent( $match=false)
Show "your edit contains spam" page with your diff and text.
An error page which can definitely be safely rendered using the OutputPage.
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
static commentBlock( $comment, $title=null, $local=false, $wikiId=null, $useParentheses=true)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Exception representing a failure to serialize or unserialize a content object.
Exception thrown when an unregistered content model is requested.
Class for creating new log entries and inserting them into the database.
setPerformer(UserIdentity $performer)
Set the user that performed the action being logged.
Helper for displaying edit conflicts in text content models to users.
getEditFormHtmlAfterContent()
Content to go in the edit form after textbox1.
getEditConflictMainTextBox(array $customAttribs=[])
HTML to build the textbox1 on edit conflicts.
setContentFormat( $contentFormat)
setContentModel( $contentModel)
setTextboxes( $yourtext, $storedversion)
getEditFormHtmlBeforeContent()
Content to go in the edit form before textbox1.
Helps EditPage build textboxes.
static plaintextParam( $plaintext)
This is one of the Core classes and should be read at least once by any new developers.
addHTML( $text)
Append $text to the body HTML.
Show an error when a user tries to do something they do not have the necessary permissions for.
Variant of the Message class.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getErrors()
Get the list of errors.
isOK()
Returns whether the operation completed.
fatal( $message,... $parameters)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Handles formatting for the "templates used on this page" lists.
Show an error when the user hits a rate limit.
Represents a title within MediaWiki.
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Show an error when the user tries to do something whilst blocked.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
static isIP( $name)
Does the string match an anonymous IP address?
static getExpiryOptions(MessageLocalizer $msgLocalizer, $watchedItem)
Get options and default for a watchlist expiry select list.
static doWatchOrUnwatch( $watch, Title $title, User $user, string $expiry=null)
Watch or unwatch a page.
Class representing a MediaWiki article and history.
getContent( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
getRedirectTarget()
If this page is a redirect, get its target.
isRedirect()
Tests if the article content represents a redirect.
const CONTENT_MODEL_JAVASCRIPT
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Base interface for content objects.
Interface for objects which can provide a MediaWiki context on request.
Serves as a common repository of constants for EditPage edit status results.
const AS_RATE_LIMITED
Status: rate limiter for action 'edit' was tripped.
const AS_NO_CHANGE_CONTENT_MODEL
Status: user tried to modify the content model, but is not allowed to do that ( User::isAllowed('edit...
const AS_READ_ONLY_PAGE_LOGGED
Status: this logged in user is not allowed to edit this page.
const AS_READ_ONLY_PAGE_ANON
Status: this anonymous user is not allowed to edit this page.
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
const AS_UNICODE_NOT_SUPPORTED
Status: edit rejected because browser doesn't support Unicode.
const AS_ARTICLE_WAS_DELETED
Status: article was deleted while editing and wpRecreate == false or form was not posted.
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
const AS_PARSE_ERROR
Status: can't parse content.
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
const AS_IMAGE_REDIRECT_ANON
Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
const AS_SELF_REDIRECT
Status: user tried to create self-redirect and wpIgnoreSelfRedirect is false.
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
const AS_NO_CREATE_PERMISSION
Status: user tried to create this page, but is not allowed to do that.
const AS_IMAGE_REDIRECT_LOGGED
Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
const AS_MAX_ARTICLE_SIZE_EXCEEDED
Status: article is too big (> $wgMaxArticleSize), after merging in the new section.
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
const AS_SUMMARY_NEEDED
Status: no edit summary given and the user has forceeditsummary set and the user is not editing in hi...
const AS_HOOK_ERROR
Status: Article update aborted by a hook function.
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
const AS_CHANGE_TAG_ERROR
Status: an error relating to change tagging.
const AS_CANNOT_USE_CUSTOM_MODEL
Status: when changing the content model is disallowed due to $wgContentHandlerUseDB being false.
const AS_READ_ONLY_PAGE
Status: wiki is in readonly mode (wfReadOnly() == true)
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang