29 use Wikimedia\ScopedCallback;
416 # Placeholders for text injection by hooks (must be HTML)
417 # extensions should take care to _append_ to the present value
478 $this->mArticle = $article;
479 $this->page = $article->
getPage();
480 $this->mTitle = $article->
getTitle();
489 $this->context->setTitle( $this->mTitle );
491 $this->contentModel = $this->mTitle->getContentModel();
494 $this->contentFormat = $handler->getDefaultFormat();
495 $this->editConflictHelperFactory = [ $this,
'newTextConflictHelper' ];
527 $this->mContextTitle =
$title;
539 if ( is_null( $this->mContextTitle ) ) {
540 wfDeprecated( __METHOD__ .
' called with no title set',
'1.32' );
556 return $this->enableApiEditOverride ===
true ||
567 $this->enableApiEditOverride = $enableOverride;
583 if ( !
Hooks::run(
'AlternateEdit', [ $this ] ) ) {
587 wfDebug( __METHOD__ .
": enter\n" );
589 $request = $this->context->getRequest();
591 if ( $request->getBool(
'redlink' ) && $this->mTitle->exists() ) {
592 $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
597 $this->firsttime =
false;
602 $this->preview =
true;
606 $this->formtype =
'save';
607 } elseif ( $this->preview ) {
608 $this->formtype =
'preview';
609 } elseif ( $this->diff ) {
610 $this->formtype =
'diff';
611 }
else { # First time through
612 $this->firsttime =
true;
614 $this->formtype =
'preview';
616 $this->formtype =
'initial';
622 wfDebug( __METHOD__ .
": User can't edit\n" );
624 if ( $this->context->getUser()->getBlock() ) {
626 MediaWikiServices::getInstance()->getBlockManager()
627 ->trackBlockWithCookie( $this->context->getUser() );
632 $this->context->getUser()->spreadAnyEditBlock();
641 $revision = $this->mArticle->getRevisionFetched();
648 if ( $this->undidRev ) {
650 $prevRev = $undidRevObj ? $undidRevObj->getPrevious() :
null;
652 if ( !$this->undidRev
659 'contentmodelediterror',
660 $revision->getContentModel(),
668 $this->isConflict =
false;
670 # Show applicable editing introductions
671 if ( $this->formtype ==
'initial' || $this->firsttime ) {
675 # Attempt submission here. This will check for edit conflicts,
676 # and redundantly check for locked database, blocked IPs, etc.
677 # that edit() already checked just in case someone tries to sneak
678 # in the back door with a hand-edited submission URL.
680 if ( $this->formtype ==
'save' ) {
681 $resultDetails =
null;
688 # First time through: get contents, set time for conflict
690 if ( $this->formtype ==
'initial' || $this->firsttime ) {
695 if ( !$this->mTitle->getArticleID() ) {
696 Hooks::run(
'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
698 Hooks::run(
'EditFormInitialText', [ $this ] );
711 $user = $this->context->getUser();
712 $permErrors = $this->mTitle->getUserPermissionsErrors(
'edit', $user, $rigor );
713 # Can this title be created?
714 if ( !$this->mTitle->exists() ) {
715 $permErrors = array_merge(
718 $this->mTitle->getUserPermissionsErrors(
'create', $user, $rigor ),
723 # Ignore some permissions errors when a user is just previewing/viewing diffs
725 foreach ( $permErrors as $error ) {
726 if ( ( $this->preview || $this->diff )
728 $error[0] ==
'blockedtext' ||
729 $error[0] ==
'autoblockedtext' ||
730 $error[0] ==
'systemblockedtext'
755 $out = $this->context->getOutput();
756 if ( $this->context->getRequest()->getBool(
'redlink' ) ) {
760 $out->redirect( $this->mTitle->getFullURL() );
766 # Use the normal message if there's nothing to display
768 $action = $this->mTitle->exists() ?
'edit' :
769 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
775 $out->formatPermissionsErrorMessage( $permErrors,
'edit' )
785 $out = $this->context->getOutput();
786 Hooks::run(
'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
788 $out->setRobotPolicy(
'noindex,nofollow' );
789 $out->setPageTitle( $this->context->msg(
791 $this->getContextTitle()->getPrefixedText()
794 $out->addHTML( $this->editFormPageTop );
795 $out->addHTML( $this->editFormTextTop );
797 if ( $errorMessage !==
'' ) {
798 $out->addWikiTextAsInterface( $errorMessage );
799 $out->addHTML(
"<hr />\n" );
802 # If the user made changes, preserve them when showing the markup
803 # (This happens when a user is blocked during edit, for instance)
804 if ( !$this->firsttime ) {
806 $out->addWikiMsg(
'viewyourtext' );
811 # Serialize using the default format if the content model is not supported
812 # (e.g. for an old revision with a different model)
815 $out->addWikiMsg(
'viewsourcetext' );
818 $out->addHTML( $this->editFormTextBeforeContent );
819 $this->
showTextbox( $text,
'wpTextbox1', [
'readonly' ] );
820 $out->addHTML( $this->editFormTextAfterContent );
824 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
826 $out->addHTML( $this->editFormTextBottom );
827 if ( $this->mTitle->exists() ) {
828 $out->returnToMain(
null, $this->mTitle );
838 $config = $this->context->getConfig();
839 $previewOnOpenNamespaces = $config->get(
'PreviewOnOpenNamespaces' );
840 $request = $this->context->getRequest();
841 if ( $config->get(
'RawHtml' ) ) {
847 if ( $request->getVal(
'preview' ) ==
'yes' ) {
850 } elseif ( $request->getVal(
'preview' ) ==
'no' ) {
853 } elseif ( $this->section ==
'new' ) {
856 } elseif ( ( $request->getCheck(
'preload' ) || $this->mTitle->exists() )
857 && $this->context->getUser()->getOption(
'previewonfirst' )
861 } elseif ( !$this->mTitle->exists()
862 && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
863 && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
879 if ( $this->mTitle->isUserConfigPage() ) {
880 $name = $this->mTitle->getSkinFromConfigSubpage();
881 $skins = array_merge(
885 return !in_array( $name, $skins )
886 && in_array( strtolower( $name ), $skins );
901 return $contentHandler->supportsSections();
910 # Section edit can come from either the form or a link
911 $this->section = $request->getVal(
'wpSection', $request->getVal(
'section' ) );
914 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
917 $this->isNew = !$this->mTitle->exists() || $this->section ==
'new';
919 if ( $request->wasPosted() ) {
920 # These fields need to be checked for encoding.
921 # Also remove trailing whitespace, but don't remove _initial_
922 # whitespace from the text boxes. This may be significant formatting.
923 $this->textbox1 = rtrim( $request->getText(
'wpTextbox1' ) );
924 if ( !$request->getCheck(
'wpTextbox2' ) ) {
934 $this->unicodeCheck = $request->getText(
'wpUnicodeCheck' );
936 $this->summary = $request->getText(
'wpSummary' );
938 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
939 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
941 $this->summary = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->summary );
943 # Treat sectiontitle the same way as summary.
944 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
945 # currently doing double duty as both edit summary and section title. Right now this
946 # is just to allow API edits to work around this limitation, but this should be
947 # incorporated into the actual edit form when EditPage is rewritten (T20654, T28312).
948 $this->sectiontitle = $request->getText(
'wpSectionTitle' );
949 $this->sectiontitle = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->sectiontitle );
951 $this->edittime = $request->getVal(
'wpEdittime' );
952 $this->editRevId = $request->getIntOrNull(
'editRevId' );
953 $this->starttime = $request->getVal(
'wpStarttime' );
955 $undidRev = $request->getInt(
'wpUndidRevision' );
960 $this->scrolltop = $request->getIntOrNull(
'wpScrolltop' );
962 if ( $this->textbox1 ===
'' && !$request->getCheck(
'wpTextbox1' ) ) {
966 $this->incompleteForm =
true;
970 $this->incompleteForm = !$request->getVal(
'wpUltimateParam' );
972 if ( $this->incompleteForm ) {
973 # If the form is incomplete, force to preview.
974 wfDebug( __METHOD__ .
": Form data appears to be incomplete\n" );
975 wfDebug(
"POST DATA: " . var_export( $request->getPostValues(),
true ) .
"\n" );
976 $this->preview =
true;
978 $this->preview = $request->getCheck(
'wpPreview' );
979 $this->diff = $request->getCheck(
'wpDiff' );
985 if ( $this->
tokenOk( $request ) ) {
986 # Some browsers will not report any submit button
987 # if the user hits enter in the comment box.
988 # The unmarked state will be assumed to be a save,
989 # if the form seems otherwise complete.
990 wfDebug( __METHOD__ .
": Passed token check.\n" );
991 } elseif ( $this->diff ) {
992 # Failed token check, but only requested "Show Changes".
993 wfDebug( __METHOD__ .
": Failed token check; Show Changes requested.\n" );
995 # Page might be a hack attempt posted from
996 # an external site. Preview instead of saving.
997 wfDebug( __METHOD__ .
": Failed token check; forcing preview\n" );
998 $this->preview =
true;
1002 if ( !preg_match(
'/^\d{14}$/', $this->edittime ) ) {
1003 $this->edittime =
null;
1006 if ( !preg_match(
'/^\d{14}$/', $this->starttime ) ) {
1007 $this->starttime =
null;
1010 $this->recreate = $request->getCheck(
'wpRecreate' );
1012 $this->minoredit = $request->getCheck(
'wpMinoredit' );
1013 $this->watchthis = $request->getCheck(
'wpWatchthis' );
1015 $user = $this->context->getUser();
1016 # Don't force edit summaries when a user is editing their own user or talk page
1017 if ( ( $this->mTitle->mNamespace ==
NS_USER || $this->mTitle->mNamespace ==
NS_USER_TALK )
1018 && $this->mTitle->getText() == $user->getName()
1020 $this->allowBlankSummary =
true;
1022 $this->allowBlankSummary = $request->getBool(
'wpIgnoreBlankSummary' )
1023 || !$user->getOption(
'forceeditsummary' );
1026 $this->autoSumm = $request->getText(
'wpAutoSummary' );
1028 $this->allowBlankArticle = $request->getBool(
'wpIgnoreBlankArticle' );
1029 $this->allowSelfRedirect = $request->getBool(
'wpIgnoreSelfRedirect' );
1033 $this->changeTags = [];
1035 $this->changeTags = array_filter( array_map(
'trim', explode(
',',
1039 # Not a posted form? Start with nothing.
1040 wfDebug( __METHOD__ .
": Not a posted form.\n" );
1041 $this->textbox1 =
'';
1042 $this->summary =
'';
1043 $this->sectiontitle =
'';
1044 $this->edittime =
'';
1045 $this->editRevId =
null;
1047 $this->
edit =
false;
1048 $this->preview =
false;
1049 $this->save =
false;
1050 $this->diff =
false;
1051 $this->minoredit =
false;
1053 $this->watchthis = $request->getBool(
'watchthis',
false );
1054 $this->recreate =
false;
1058 if ( $this->section ==
'new' && $request->getVal(
'preloadtitle' ) ) {
1059 $this->sectiontitle = $request->getVal(
'preloadtitle' );
1061 $this->summary = $request->getVal(
'preloadtitle' );
1062 } elseif ( $this->section !=
'new' && $request->getVal(
'summary' ) !==
'' ) {
1063 $this->summary = $request->getText(
'summary' );
1064 if ( $this->summary !==
'' ) {
1065 $this->hasPresetSummary =
true;
1069 if ( $request->getVal(
'minor' ) ) {
1070 $this->minoredit =
true;
1074 $this->oldid = $request->getInt(
'oldid' );
1075 $this->parentRevId = $request->getInt(
'parentRevId' );
1077 $this->bot = $request->getBool(
'bot',
true );
1078 $this->nosummary = $request->getBool(
'nosummary' );
1081 $this->contentModel = $request->getText(
'model', $this->contentModel );
1083 $this->contentFormat = $request->getText(
'format', $this->contentFormat );
1089 'editpage-invalidcontentmodel-title',
1090 'editpage-invalidcontentmodel-text',
1095 if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1097 'editpage-notsupportedcontentformat-title',
1098 'editpage-notsupportedcontentformat-text',
1112 $this->editintro = $request->getText(
'editintro',
1114 $this->section ===
'new' ?
'MediaWiki:addsection-editintro' :
'' );
1117 Hooks::run(
'EditPage::importFormData', [ $this, $request ] );
1139 $this->edittime = $this->page->getTimestamp();
1140 $this->editRevId = $this->page->getLatest();
1144 $out = $this->context->getOutput();
1145 if ( $out->getRedirect() ===
'' ) {
1153 $modelName = $modelMsg->exists() ? $modelMsg->text() :
$content->getModel();
1155 $out = $this->context->getOutput();
1156 $out->showErrorPage(
1157 'modeleditnotsupported-title',
1158 'modeleditnotsupported-text',
1166 $user = $this->context->getUser();
1168 # Sort out the "watch" checkbox
1169 if ( $user->getOption(
'watchdefault' ) ) {
1171 $this->watchthis =
true;
1172 } elseif ( $user->getOption(
'watchcreations' ) && !$this->mTitle->exists() ) {
1174 $this->watchthis =
true;
1175 } elseif ( $user->isWatched( $this->mTitle ) ) {
1177 $this->watchthis =
true;
1179 if ( $user->getOption(
'minordefault' ) && !
$this->isNew ) {
1180 $this->minoredit =
true;
1182 if ( $this->textbox1 ===
false ) {
1200 $user = $this->context->getUser();
1201 $request = $this->context->getRequest();
1204 if ( !$this->mTitle->exists() || $this->section ==
'new' ) {
1205 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && $this->section !=
'new' ) {
1206 # If this is a system message, get the default text.
1207 $msg = $this->mTitle->getDefaultMessageText();
1212 # If requested, preload some text.
1213 $preload = $request->getVal(
'preload',
1215 $this->section ===
'new' ?
'MediaWiki:addsection-preload' :
'' );
1216 $params = $request->getArray(
'preloadparams', [] );
1221 } elseif ( $this->section !=
'' ) {
1224 $content = $orig ? $orig->getSection( $this->section ) :
null;
1230 $undoafter = $request->getInt(
'undoafter' );
1231 $undo = $request->getInt(
'undo' );
1233 if ( $undo > 0 && $undoafter > 0 ) {
1238 # Sanity check, make sure it's the right page,
1239 # the revisions exist and they were not deleted.
1240 # Otherwise, $content will be left as-is.
1241 if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1242 !$undorev->isDeleted( RevisionRecord::DELETED_TEXT ) &&
1243 !$oldrev->isDeleted( RevisionRecord::DELETED_TEXT )
1249 $this->context->getOutput()->redirect( $this->mTitle->getFullURL( [
1250 'action' =>
'mcrundo',
1252 'undoafter' => $undoafter,
1256 $content = $this->page->getUndoContent( $undorev, $oldrev );
1259 # Warn the user that something went wrong
1260 $undoMsg =
'failure';
1264 if ( $undoMsg ===
null ) {
1265 $oldContent = $this->page->getContent( RevisionRecord::RAW );
1267 $user, MediaWikiServices::getInstance()->getContentLanguage() );
1268 $newContent =
$content->preSaveTransform( $this->mTitle, $user, $popts );
1269 if ( $newContent->getModel() !== $oldContent->getModel() ) {
1274 $this->contentModel = $newContent->getModel();
1275 $this->contentFormat = $oldrev->getContentFormat();
1278 if ( $newContent->equals( $oldContent ) ) {
1279 # Tell the user that the undo results in no change,
1280 # i.e. the revisions were already undone.
1281 $undoMsg =
'nochange';
1284 # Inform the user of our success and set an automatic edit summary
1285 $undoMsg =
'success';
1287 # If we just undid one rev, use an autosummary
1288 $firstrev = $oldrev->getNext();
1289 if ( $firstrev && $firstrev->getId() == $undo ) {
1290 $userText = $undorev->getUserText();
1291 if ( $userText ===
'' ) {
1292 $undoSummary = $this->context->msg(
1293 'undo-summary-username-hidden',
1295 )->inContentLanguage()->text();
1298 'undo-summary-anon' :
1300 $undoSummary = $this->context->msg(
1304 )->inContentLanguage()->text();
1306 if ( $this->summary ===
'' ) {
1307 $this->summary = $undoSummary;
1309 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1312 $this->undidRev = $undo;
1314 $this->formtype =
'diff';
1324 $out = $this->context->getOutput();
1327 $class = ( $undoMsg ==
'success' ?
'' :
'error ' ) .
"mw-undo-{$undoMsg}";
1328 $this->editFormPageTop .= Html::rawElement(
1329 'div', [
'class' => $class ],
1330 $out->parseAsInterface(
1331 $this->context->msg(
'undo-' . $undoMsg )->plain()
1340 $curRevision = $this->page->getRevision();
1341 $oldRevision = $this->mArticle->getRevisionFetched();
1345 && $curRevision->getId() !== $oldRevision->getId()
1349 $this->context->getOutput()->redirect(
1350 $this->mTitle->getFullURL(
1352 'action' =>
'mcrrestore',
1353 'restore' => $oldRevision->getId(),
1386 if ( $this->section ==
'new' ) {
1389 $revision = $this->mArticle->getRevisionFetched();
1390 if ( $revision ===
null ) {
1392 return $handler->makeEmptyContent();
1394 $content = $revision->getContent( RevisionRecord::FOR_THIS_USER, $user );
1411 if ( $this->parentRevId ) {
1414 return $this->mArticle->getRevIdFetched();
1427 $rev = $this->page->getRevision();
1428 $content = $rev ? $rev->getContent( RevisionRecord::RAW ) :
null;
1432 return $handler->makeEmptyContent();
1433 } elseif ( !$this->undidRev ) {
1438 $logger = LoggerFactory::getInstance(
'editpage' );
1439 if ( $this->contentModel !== $rev->getContentModel() ) {
1440 $logger->warning(
"Overriding content model from current edit {prev} to {new}", [
1441 'prev' => $this->contentModel,
1442 'new' => $rev->getContentModel(),
1443 'title' => $this->
getTitle()->getPrefixedDBkey(),
1444 'method' => __METHOD__
1446 $this->contentModel = $rev->getContentModel();
1451 if ( !
$content->isSupportedFormat( $this->contentFormat ) ) {
1452 $logger->warning(
"Current revision content format unsupported. Overriding {prev} to {new}", [
1454 'prev' => $this->contentFormat,
1455 'new' => $rev->getContentFormat(),
1456 'title' => $this->
getTitle()->getPrefixedDBkey(),
1457 'method' => __METHOD__
1459 $this->contentFormat = $rev->getContentFormat();
1488 if ( !empty( $this->mPreloadContent ) ) {
1494 if ( $preload ===
'' ) {
1495 return $handler->makeEmptyContent();
1498 $user = $this->context->getUser();
1501 # Check for existence to avoid getting MediaWiki:Noarticletext
1504 return $handler->makeEmptyContent();
1513 return $handler->makeEmptyContent();
1523 return $handler->makeEmptyContent();
1526 if (
$content->getModel() !== $handler->getModelID() ) {
1527 $converted =
$content->convert( $handler->getModelID() );
1529 if ( !$converted ) {
1531 wfDebug(
"Attempt to preload incompatible content: " .
1532 "can't convert " .
$content->getModel() .
1533 " to " . $handler->getModelID() );
1535 return $handler->makeEmptyContent();
1541 return $content->preloadTransform(
$title, $parserOptions, $params );
1554 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1556 return $title &&
$title->exists() && $permissionManager->userCan(
'read', $user,
$title );
1567 $token = $request->getVal(
'wpEditToken' );
1568 $user = $this->context->getUser();
1569 $this->mTokenOk = $user->matchEditToken( $token );
1570 $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1589 $revisionId = $this->page->getLatest();
1590 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1593 if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1595 } elseif ( $this->oldid ) {
1599 $response = $this->context->getRequest()->response();
1600 $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1615 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1616 $bot = $permissionManager->userHasRight( $this->context->getUser(),
'bot' ) && $this->bot;
1619 Hooks::run(
'EditPage::attemptSave:after', [ $this,
$status, $resultDetails ] );
1628 if ( $this->context->getRequest()->getText(
'mode' ) !==
'conflict' ) {
1649 if (
$status->value == self::AS_SUCCESS_UPDATE
1650 ||
$status->value == self::AS_SUCCESS_NEW_ARTICLE
1654 $this->didSave =
true;
1655 if ( !$resultDetails[
'nullEdit'] ) {
1660 $out = $this->context->getOutput();
1664 $request = $this->context->getRequest();
1665 $extraQueryRedirect = $request->getVal(
'wpExtraQueryRedirect' );
1686 $out->wrapWikiTextAsInterface(
'error',
1687 $status->getWikiText(
false,
false, $this->context->getLanguage() )
1692 $query = $resultDetails[
'redirect'] ?
'redirect=no' :
'';
1693 if ( $extraQueryRedirect ) {
1694 if ( $query !==
'' ) {
1697 $query .= $extraQueryRedirect;
1699 $anchor = $resultDetails[
'sectionanchor'] ??
'';
1700 $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1705 $sectionanchor = $resultDetails[
'sectionanchor'];
1709 'ArticleUpdateBeforeRedirect',
1710 [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1713 if ( $resultDetails[
'redirect'] ) {
1714 if ( $extraQuery !==
'' ) {
1715 $extraQuery =
'&' . $extraQuery;
1717 $extraQuery =
'redirect=no' . $extraQuery;
1719 if ( $extraQueryRedirect ) {
1720 if ( $extraQuery !==
'' ) {
1723 $extraQuery .= $extraQueryRedirect;
1726 $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1751 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
1762 $this->hookError =
'<div class="error">' .
"\n" .
1763 $status->getWikiText(
false,
false, $this->context->getLanguage() ) .
1780 if ( $this->hookError !=
'' ) {
1781 # ...or the hook could be expecting us to produce an error
1782 $status->fatal(
'hookaborted' );
1790 $user, $this->minoredit ] )
1792 # Error messages etc. could be handled within the hook...
1794 $status->fatal(
'hookaborted' );
1807 } elseif ( !
$status->isOK() ) {
1808 # ...or the hook could be expecting us to produce an error
1810 if ( !
$status->getErrors() ) {
1812 $status->fatal(
'hookaborted' );
1829 $errmsg =
$status->getWikiText(
1832 $this->context->getLanguage()
1835 <div
class=
"errorbox">
1849 if ( $this->sectiontitle !==
'' ) {
1854 if ( $this->summary ===
'' ) {
1855 $cleanSectionTitle = MediaWikiServices::getInstance()->getParser()
1856 ->stripSectionName( $this->sectiontitle );
1857 return $this->context->msg(
'newsectionsummary' )
1858 ->plaintextParams( $cleanSectionTitle )->inContentLanguage()->text();
1860 } elseif ( $this->summary !==
'' ) {
1862 # This is a new section, so create a link to the new section
1863 # in the revision summary.
1864 $cleanSummary = MediaWikiServices::getInstance()->getParser()
1865 ->stripSectionName( $this->summary );
1866 return $this->context->msg(
'newsectionsummary' )
1867 ->plaintextParams( $cleanSummary )->inContentLanguage()->text();
1898 $user = $this->context->getUser();
1899 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1901 if ( !
Hooks::run(
'EditPage::attemptSave', [ $this ] ) ) {
1902 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving\n" );
1903 $status->fatal(
'hookaborted' );
1908 if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1909 $status->fatal(
'unicode-support-fail' );
1914 $request = $this->context->getRequest();
1915 $spam = $request->getText(
'wpAntispam' );
1916 if ( $spam !==
'' ) {
1921 $this->mTitle->getPrefixedText() .
1922 '" submitted bogus field "' .
1926 $status->fatal(
'spamprotectionmatch',
false );
1932 # Construct Content object
1936 'content-failed-to-parse',
1937 $this->contentModel,
1938 $this->contentFormat,
1945 # Check image redirect
1946 if ( $this->mTitle->getNamespace() ==
NS_FILE &&
1947 $textbox_content->isRedirect() &&
1948 !$permissionManager->userHasRight( $user,
'upload' )
1951 $status->setResult(
false, $code );
1958 if ( $match ===
false && $this->section ==
'new' ) {
1959 # $wgSpamRegex is enforced on this new heading/summary because, unlike
1960 # regular summaries, it is added to the actual wikitext.
1961 if ( $this->sectiontitle !==
'' ) {
1962 # This branch is taken when the API is used with the 'sectiontitle' parameter.
1965 # This branch is taken when the "Add Topic" user interface is used, or the API
1966 # is used with the 'summary' parameter.
1970 if ( $match ===
false ) {
1973 if ( $match !==
false ) {
1974 $result[
'spam'] = $match;
1975 $ip = $request->getIP();
1976 $pdbk = $this->mTitle->getPrefixedDBkey();
1977 $match = str_replace(
"\n",
'', $match );
1978 wfDebugLog(
'SpamRegex',
"$ip spam regex hit [[$pdbk]]: \"$match\"" );
1979 $status->fatal(
'spamprotectionmatch', $match );
1985 [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1987 # Error messages etc. could be handled within the hook...
1988 $status->fatal(
'hookaborted' );
1991 } elseif ( $this->hookError !=
'' ) {
1992 # ...or the hook could be expecting us to produce an error
1993 $status->fatal(
'hookaborted' );
1998 if ( $permissionManager->isBlockedFrom( $user, $this->mTitle ) ) {
2001 $user->spreadAnyEditBlock();
2003 # Check block state against master, thus 'false'.
2004 $status->setResult(
false, self::AS_BLOCKED_PAGE_FOR_USER );
2008 $this->contentLength = strlen( $this->textbox1 );
2009 $config = $this->context->getConfig();
2010 $maxArticleSize = $config->get(
'MaxArticleSize' );
2011 if ( $this->contentLength > $maxArticleSize * 1024 ) {
2013 $this->tooBig =
true;
2014 $status->setResult(
false, self::AS_CONTENT_TOO_BIG );
2018 if ( !$permissionManager->userHasRight( $user,
'edit' ) ) {
2019 if ( $user->isAnon() ) {
2020 $status->setResult(
false, self::AS_READ_ONLY_PAGE_ANON );
2023 $status->fatal(
'readonlytext' );
2029 $changingContentModel =
false;
2030 if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
2031 if ( !$config->get(
'ContentHandlerUseDB' ) ) {
2032 $status->fatal(
'editpage-cannot-use-custom-model' );
2035 } elseif ( !$permissionManager->userHasRight( $user,
'editcontentmodel' ) ) {
2036 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
2043 $canEditModel = $permissionManager->userCan(
2046 $titleWithNewContentModel
2051 || !$permissionManager->userCan(
'edit', $user, $titleWithNewContentModel )
2053 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
2058 $changingContentModel =
true;
2059 $oldContentModel = $this->mTitle->getContentModel();
2062 if ( $this->changeTags ) {
2064 $this->changeTags, $user );
2065 if ( !$changeTagsStatus->isOK() ) {
2067 return $changeTagsStatus;
2072 $status->fatal(
'readonlytext' );
2076 if ( $user->pingLimiter() || $user->pingLimiter(
'linkpurge', 0 )
2077 || ( $changingContentModel && $user->pingLimiter(
'editcontentmodel' ) )
2079 $status->fatal(
'actionthrottledtext' );
2084 # If the article has been deleted while editing, don't save it without
2087 $status->setResult(
false, self::AS_ARTICLE_WAS_DELETED );
2091 # Load the page data from the master. If anything changes in the meantime,
2092 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
2093 $this->page->loadPageData(
'fromdbmaster' );
2094 $new = !$this->page->exists();
2098 if ( !$permissionManager->userCan(
'create', $user, $this->mTitle ) ) {
2099 $status->fatal(
'nocreatetext' );
2101 wfDebug( __METHOD__ .
": no create permission\n" );
2108 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2109 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI && $defaultMessageText !== false ) {
2110 $defaultText = $defaultMessageText;
2115 if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
2116 $this->blankArticle =
true;
2117 $status->fatal(
'blankarticle' );
2118 $status->setResult(
false, self::AS_BLANK_ARTICLE );
2128 $result[
'sectionanchor'] =
'';
2129 if ( $this->section ==
'new' ) {
2130 if ( $this->sectiontitle !==
'' ) {
2133 } elseif ( $this->summary !==
'' ) {
2144 # Article exists. Check for edit conflict.
2146 $this->page->clear(); # Force reload of dates, etc.
2147 $timestamp = $this->page->getTimestamp();
2148 $latest = $this->page->getLatest();
2150 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2156 if ( $timestamp != $this->edittime
2157 || ( $this->editRevId !==
null && $this->editRevId != $latest )
2159 $this->isConflict =
true;
2160 if ( $this->section ==
'new' ) {
2161 if ( $this->page->getUserText() == $user->getName() &&
2168 .
": duplicate new section submission; trigger edit conflict!\n" );
2171 $this->isConflict =
false;
2172 wfDebug( __METHOD__ .
": conflict suppressed; new section\n" );
2174 } elseif ( $this->section ==
''
2176 DB_MASTER, $this->mTitle->getArticleID(),
2180 # Suppress edit conflict with self, except for section edits where merging is required.
2181 wfDebug( __METHOD__ .
": Suppressing edit conflict, same user.\n" );
2182 $this->isConflict =
false;
2187 if ( $this->sectiontitle !==
'' ) {
2195 if ( $this->isConflict ) {
2197 .
": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2198 .
" (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2201 if ( $this->editRevId !==
null ) {
2202 $content = $this->page->replaceSectionAtRev(
2209 $content = $this->page->replaceSectionContent(
2217 wfDebug( __METHOD__ .
": getting section '{$this->section}'\n" );
2218 $content = $this->page->replaceSectionContent(
2226 wfDebug( __METHOD__ .
": activating conflict; section replace failed.\n" );
2227 $this->isConflict =
true;
2229 } elseif ( $this->isConflict ) {
2233 $this->isConflict =
false;
2234 wfDebug( __METHOD__ .
": Suppressing edit conflict, successful merge.\n" );
2236 $this->section =
'';
2238 wfDebug( __METHOD__ .
": Keeping edit conflict, failed merge.\n" );
2242 if ( $this->isConflict ) {
2243 $status->setResult(
false, self::AS_CONFLICT_DETECTED );
2251 if ( $this->section ==
'new' ) {
2253 if ( !$this->allowBlankSummary && trim( $this->summary ) ==
'' ) {
2254 $this->missingSummary =
true;
2255 $status->fatal(
'missingsummary' );
2261 if ( $this->textbox1 ==
'' ) {
2262 $this->missingComment =
true;
2263 $status->fatal(
'missingcommenttext' );
2267 } elseif ( !$this->allowBlankSummary
2268 && !
$content->equals( $this->getOriginalContent( $user ) )
2272 $this->missingSummary =
true;
2273 $status->fatal(
'missingsummary' );
2279 $sectionanchor =
'';
2280 if ( $this->section ==
'new' ) {
2282 } elseif ( $this->section !=
'' ) {
2283 # Try to get a section anchor from the section source, redirect
2284 # to edited section if header found.
2285 # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2286 # for duplicate heading checking and maybe parsing.
2287 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
2288 # We can't deal with anchors, includes, html etc in the header for now,
2289 # headline would need to be parsed to improve this.
2290 if ( $hasmatch && strlen(
$matches[2] ) > 0 ) {
2294 $result[
'sectionanchor'] = $sectionanchor;
2301 $this->section =
'';
2306 if ( !$this->allowSelfRedirect
2312 if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2313 $this->selfRedirect =
true;
2314 $status->fatal(
'selfredirect' );
2322 if ( $this->contentLength > $maxArticleSize * 1024 ) {
2323 $this->tooBig =
true;
2324 $status->setResult(
false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2330 ( ( $this->minoredit && !$this->isNew ) ?
EDIT_MINOR : 0 ) |
2333 $doEditStatus = $this->page->doEditContent(
2344 if ( !$doEditStatus->isOK() ) {
2348 $errors = $doEditStatus->getErrorsArray();
2349 if ( in_array( $errors[0][0],
2350 [
'edit-gone-missing',
'edit-conflict',
'edit-already-exists' ] )
2352 $this->isConflict =
true;
2356 return $doEditStatus;
2359 $result[
'nullEdit'] = $doEditStatus->hasMessage(
'edit-no-change' );
2360 if ( $result[
'nullEdit'] ) {
2362 $user->pingLimiter(
'linkpurge' );
2364 $result[
'redirect'] =
$content->isRedirect();
2369 if ( $changingContentModel ) {
2372 $new ?
false : $oldContentModel,
2373 $this->contentModel,
2388 $new = $oldModel ===
false;
2389 $log =
new ManualLogEntry(
'contentmodel', $new ?
'new' :
'change' );
2390 $log->setPerformer( $user );
2391 $log->setTarget( $this->mTitle );
2392 $log->setComment( $reason );
2393 $log->setParameters( [
2394 '4::oldmodel' => $oldModel,
2395 '5::newmodel' => $newModel
2397 $logid = $log->insert();
2398 $log->publish( $logid );
2405 $user = $this->context->getUser();
2406 if ( !$user->isLoggedIn() ) {
2438 $baseContent = $baseRevision ? $baseRevision->getContent() :
null;
2440 if ( is_null( $baseContent ) ) {
2446 $currentContent = $currentRevision ? $currentRevision->getContent() :
null;
2448 if ( is_null( $currentContent ) ) {
2454 $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2457 $editContent = $result;
2459 $this->parentRevId = $currentRevision->getId();
2479 if ( !$this->mBaseRevision ) {
2481 $this->mBaseRevision = $this->editRevId
2521 foreach ( $regexes as $regex ) {
2523 if ( preg_match( $regex, $text,
$matches ) ) {
2531 $out = $this->context->getOutput();
2533 $out->addModules(
'mediawiki.action.edit' );
2534 $out->addModuleStyles(
'mediawiki.action.edit.styles' );
2535 $out->addModuleStyles(
'mediawiki.editfont.styles' );
2537 $user = $this->context->getUser();
2539 if ( $user->getOption(
'uselivepreview' ) ) {
2540 $out->addModules(
'mediawiki.action.edit.preview' );
2543 if ( $user->getOption(
'useeditwarning' ) ) {
2544 $out->addModules(
'mediawiki.action.edit.editWarning' );
2547 # Enabled article-related sidebar, toplinks, etc.
2548 $out->setArticleRelated(
true );
2551 if ( $this->isConflict ) {
2552 $msg =
'editconflict';
2553 } elseif ( $contextTitle->exists() && $this->section !=
'' ) {
2554 $msg = $this->section ==
'new' ?
'editingcomment' :
'editingsection';
2556 $msg = $contextTitle->exists()
2558 && $contextTitle->getDefaultMessageText() !== false
2564 # Use the title defined by DISPLAYTITLE magic word when present
2565 # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2566 # setPageTitle() treats the input as wikitext, which should be safe in either case.
2567 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() :
false;
2568 if ( $displayTitle ===
false ) {
2569 $displayTitle = $contextTitle->getPrefixedText();
2571 $out->setDisplayTitle( $displayTitle );
2573 $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2575 $config = $this->context->getConfig();
2577 # Transmit the name of the message to JavaScript for live preview
2578 # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2579 $out->addJsConfigVars( [
2580 'wgEditMessage' => $msg,
2581 'wgAjaxEditStash' => $config->get(
'AjaxEditStash' ),
2586 $out->addJsConfigVars(
2587 'wgEditSubmitButtonLabelPublish',
2588 $config->get(
'EditSubmitButtonLabelPublish' )
2596 if ( $this->suppressIntro ) {
2600 $out = $this->context->getOutput();
2601 $namespace = $this->mTitle->getNamespace();
2604 # Show a warning if editing an interface message
2605 $out->wrapWikiMsg(
"<div class='mw-editinginterface'>\n$1\n</div>",
'editinginterface' );
2606 # If this is a default message (but not css, json, or js),
2607 # show a hint that it is translatable on translatewiki.net
2613 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2614 if ( $defaultMessageText !==
false ) {
2615 $out->wrapWikiMsg(
"<div class='mw-translateinterface'>\n$1\n</div>",
2616 'translateinterface' );
2619 } elseif ( $namespace ==
NS_FILE ) {
2620 # Show a hint to shared repo
2621 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $this->mTitle );
2623 $descUrl =
$file->getDescriptionUrl();
2624 # there must be a description url to show a hint to shared repo
2626 if ( !$this->mTitle->exists() ) {
2627 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2628 'sharedupload-desc-create',
$file->getRepo()->getDisplayName(), $descUrl
2631 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2632 'sharedupload-desc-edit',
$file->getRepo()->getDisplayName(), $descUrl
2639 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2640 # Show log extract when the user is currently blocked
2642 $username = explode(
'/', $this->mTitle->getText(), 2 )[0];
2645 $block = DatabaseBlock::newFromTarget( $user, $user );
2646 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { #
User does not exist
2647 $out->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2650 !is_null( $block ) &&
2651 $block->getType() != DatabaseBlock::TYPE_AUTO &&
2652 ( $block->isSitewide() || $user->isBlockedFrom( $this->mTitle ) )
2659 MediaWikiServices::getInstance()->getNamespaceInfo()->
2660 getCanonicalName(
NS_USER ) .
':' . $block->getTarget(),
2664 'showIfEmpty' =>
false,
2666 'blocked-notice-logextract',
2667 $user->getName() # Support GENDER in notice
2673 # Try to add a custom edit intro, or use the standard one if this is not possible.
2676 $this->context->msg(
'helppage' )->inContentLanguage()->text()
2678 if ( $this->context->getUser()->isLoggedIn() ) {
2681 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2690 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2692 'newarticletextanon',
2698 # Give a notice if the user is editing a deleted/moved page...
2699 if ( !$this->mTitle->exists() ) {
2706 'conds' => [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ],
2707 'showIfEmpty' =>
false,
2708 'msgKey' => [
'recreate-moveddeleted-warn' ]
2720 if ( $this->editintro ) {
2724 $this->context->getOutput()->addWikiTextAsContent(
2725 '<div class="mw-editintro">{{:' .
$title->getFullText() .
'}}</div>',
2762 return $content->serialize( $this->contentFormat );
2782 if ( $text ===
false || $text ===
null ) {
2787 $this->contentModel, $this->contentFormat );
2805 # need to parse the preview early so that we know which templates are used,
2806 # otherwise users with "show preview after edit box" will get a blank list
2807 # we parse this near the beginning so that setHeaders can do the title
2808 # setting work instead of leaving it in getPreviewText
2809 $previewOutput =
'';
2810 if ( $this->formtype ==
'preview' ) {
2814 $out = $this->context->getOutput();
2818 Hooks::run(
'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
2825 if ( !$this->isConflict &&
2826 $this->section !=
'' &&
2831 $out->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
2837 $out->addHTML( $this->editFormPageTop );
2839 $user = $this->context->getUser();
2840 if ( $user->getOption(
'previewontop' ) ) {
2844 $out->addHTML( $this->editFormTextTop );
2847 $out->wrapWikiMsg(
"<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2848 'deletedwhileediting' );
2853 $out->addHTML( Html::openElement(
2856 'class' =>
'mw-editform',
2857 'id' => self::EDITFORM_ID,
2858 'name' => self::EDITFORM_ID,
2861 'enctype' =>
'multipart/form-data'
2865 if ( is_callable( $formCallback ) ) {
2866 wfWarn(
'The $formCallback parameter to ' . __METHOD__ .
'is deprecated' );
2867 call_user_func_array( $formCallback, [ &$out ] );
2871 $out->addHTML( Html::hidden(
'wpUnicodeCheck', self::UNICODE_CHECK ) );
2875 Xml::openElement(
'div', [
'id' =>
'antispam-container',
'style' =>
'display: none;' ] )
2878 [
'for' =>
'wpAntispam' ],
2879 $this->context->msg(
'simpleantispam-label' )->parse()
2885 'name' =>
'wpAntispam',
2886 'id' =>
'wpAntispam',
2895 Hooks::run(
'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
2901 $username = $this->lastDelete->user_name;
2903 ->getComment(
'log_comment', $this->lastDelete )->text;
2907 $key = $comment ===
''
2908 ?
'confirmrecreate-noreason'
2909 :
'confirmrecreate';
2911 '<div class="mw-confirm-recreate">' .
2912 $this->context->msg( $key, $username,
"<nowiki>$comment</nowiki>" )->parse() .
2913 Xml::checkLabel( $this->context->msg(
'recreate' )->text(),
'wpRecreate',
'wpRecreate',
false,
2920 # When the summary is hidden, also hide them on preview/show changes
2921 if ( $this->nosummary ) {
2922 $out->addHTML( Html::hidden(
'nosummary',
true ) );
2925 # If a blank edit summary was previously provided, and the appropriate
2926 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2927 # user being bounced back more than once in the event that a summary
2930 # For a bit more sophisticated detection of blank summaries, hash the
2931 # automatic one and pass that in the hidden field wpAutoSummary.
2932 if ( $this->missingSummary || ( $this->section ==
'new' && $this->nosummary ) ) {
2933 $out->addHTML( Html::hidden(
'wpIgnoreBlankSummary',
true ) );
2936 if ( $this->undidRev ) {
2937 $out->addHTML( Html::hidden(
'wpUndidRevision', $this->undidRev ) );
2940 if ( $this->selfRedirect ) {
2941 $out->addHTML( Html::hidden(
'wpIgnoreSelfRedirect',
true ) );
2944 if ( $this->hasPresetSummary ) {
2948 $this->autoSumm = md5(
'' );
2951 $autosumm = $this->autoSumm !==
'' ? $this->autoSumm : md5( $this->summary );
2952 $out->addHTML( Html::hidden(
'wpAutoSummary', $autosumm ) );
2954 $out->addHTML( Html::hidden(
'oldid', $this->oldid ) );
2955 $out->addHTML( Html::hidden(
'parentRevId', $this->
getParentRevId() ) );
2957 $out->addHTML( Html::hidden(
'format', $this->contentFormat ) );
2958 $out->addHTML( Html::hidden(
'model', $this->contentModel ) );
2962 if ( $this->section ==
'new' ) {
2967 $out->addHTML( $this->editFormTextBeforeContent );
2968 if ( $this->isConflict ) {
2983 if ( !$this->mTitle->isUserConfigPage() ) {
2984 $out->addHTML( self::getEditToolbar() );
2987 if ( $this->blankArticle ) {
2988 $out->addHTML( Html::hidden(
'wpIgnoreBlankArticle',
true ) );
2991 if ( $this->isConflict ) {
2996 $conflictTextBoxAttribs = [];
2998 $conflictTextBoxAttribs[
'style'] =
'display:none;';
2999 } elseif ( $this->isOldRev ) {
3000 $conflictTextBoxAttribs[
'class'] =
'mw-textarea-oldrev';
3009 $out->addHTML( $this->editFormTextAfterContent );
3019 $out->addHTML( $this->editFormTextAfterTools .
"\n" );
3023 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'hiddencats' ],
3026 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'limitreport' ],
3027 self::getPreviewLimitReport( $this->mParserOutput ) ) );
3029 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
3031 if ( $this->isConflict ) {
3036 $msg = $this->context->msg(
3037 'content-failed-to-parse',
3038 $this->contentModel,
3039 $this->contentFormat,
3042 $out->wrapWikiTextAsInterface(
'error', $msg->plain() );
3047 if ( $this->isConflict ) {
3049 } elseif ( $this->preview ) {
3051 } elseif ( $this->diff ) {
3056 $out->addHTML( Html::hidden(
'mode', $mode, [
'id' =>
'mw-edit-mode' ] ) );
3060 $out->addHTML( Html::hidden(
'wpUltimateParam',
true ) );
3061 $out->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
3063 if ( !$user->getOption(
'previewontop' ) ) {
3077 $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
3082 if ( $this->preview ) {
3084 } elseif ( $this->section !=
'' ) {
3088 return Html::rawElement(
'div', [
'class' =>
'templatesUsed' ],
3089 $templateListFormatter->format( $templates,
$type )
3100 preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches );
3102 return MediaWikiServices::getInstance()->getParser()
3103 ->stripSectionName( trim(
$matches[2] ) );
3110 $out = $this->context->getOutput();
3111 $user = $this->context->getUser();
3112 if ( $this->isConflict ) {
3114 $this->editRevId = $this->page->getLatest();
3116 if ( $this->section !=
'' && $this->section !=
'new' && !$this->summary &&
3117 !$this->preview && !$this->diff
3120 if ( $sectionTitle !==
false ) {
3121 $this->summary =
"/* $sectionTitle */ ";
3127 if ( $this->missingComment ) {
3128 $out->wrapWikiMsg(
"<div id='mw-missingcommenttext'>\n$1\n</div>",
'missingcommenttext' );
3131 if ( $this->missingSummary && $this->section !=
'new' ) {
3133 "<div id='mw-missingsummary'>\n$1\n</div>",
3134 [
'missingsummary', $buttonLabel ]
3138 if ( $this->missingSummary && $this->section ==
'new' ) {
3140 "<div id='mw-missingcommentheader'>\n$1\n</div>",
3141 [
'missingcommentheader', $buttonLabel ]
3145 if ( $this->blankArticle ) {
3147 "<div id='mw-blankarticle'>\n$1\n</div>",
3148 [
'blankarticle', $buttonLabel ]
3152 if ( $this->selfRedirect ) {
3154 "<div id='mw-selfredirect'>\n$1\n</div>",
3155 [
'selfredirect', $buttonLabel ]
3159 if ( $this->hookError !==
'' ) {
3160 $out->addWikiTextAsInterface( $this->hookError );
3163 if ( $this->section !=
'new' ) {
3164 $revision = $this->mArticle->getRevisionFetched();
3168 if ( !$revision->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
3170 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3171 'rev-deleted-text-permission'
3173 } elseif ( $revision->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
3175 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3176 'rev-deleted-text-view'
3180 if ( !$revision->isCurrent() ) {
3181 $this->mArticle->setOldSubtitle( $revision->getId() );
3183 Html::warningBox(
"\n$1\n" ),
3186 $this->isOldRev =
true;
3188 } elseif ( $this->mTitle->exists() ) {
3191 $out->wrapWikiMsg(
"<div class='errorbox'>\n$1\n</div>\n",
3192 [
'missing-revision', $this->oldid ] );
3199 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3202 } elseif ( $user->isAnon() ) {
3203 if ( $this->formtype !=
'preview' ) {
3204 $returntoquery = array_diff_key(
3205 $this->context->getRequest()->getValues(),
3206 [
'title' =>
true,
'returnto' =>
true,
'returntoquery' =>
true ]
3209 "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3210 [
'anoneditwarning',
3213 'returnto' => $this->
getTitle()->getPrefixedDBkey(),
3218 'returnto' => $this->
getTitle()->getPrefixedDBkey(),
3224 $out->wrapWikiMsg(
"<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3225 'anonpreviewwarning'
3228 } elseif ( $this->mTitle->isUserConfigPage() ) {
3229 # Check the skin exists
3232 "<div class='error' id='mw-userinvalidconfigtitle'>\n$1\n</div>",
3233 [
'userinvalidconfigtitle', $this->mTitle->getSkinFromConfigSubpage() ]
3236 if ( $this->
getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3237 $isUserCssConfig = $this->mTitle->isUserCssConfigPage();
3238 $isUserJsonConfig = $this->mTitle->isUserJsonConfigPage();
3239 $isUserJsConfig = $this->mTitle->isUserJsConfigPage();
3241 $warning = $isUserCssConfig
3243 : ( $isUserJsonConfig ?
'userjsonispublic' :
'userjsispublic' );
3245 $out->wrapWikiMsg(
'<div class="mw-userconfigpublic">$1</div>', $warning );
3247 if ( $this->formtype !==
'preview' ) {
3248 $config = $this->context->getConfig();
3249 if ( $isUserCssConfig && $config->get(
'AllowUserCss' ) ) {
3251 "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3252 [
'usercssyoucanpreview' ]
3254 } elseif ( $isUserJsonConfig ) {
3256 "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>",
3257 [
'userjsonyoucanpreview' ]
3259 } elseif ( $isUserJsConfig && $config->get(
'AllowUserJs' ) ) {
3261 "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3262 [
'userjsyoucanpreview' ]
3273 # Add header copyright warning
3288 return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3289 'id' =>
'wpSummary',
3290 'name' =>
'wpSummary',
3294 'spellcheck' =>
'true',
3308 $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3317 $inputAttrs[
'inputId'] = $inputAttrs[
'id'];
3318 $inputAttrs[
'id'] =
'wpSummaryWidget';
3320 return new OOUI\FieldLayout(
3321 new OOUI\TextInputWidget( [
3323 'infusable' =>
true,
3326 'label' =>
new OOUI\HtmlSnippet( $labelText ),
3328 'id' =>
'wpSummaryLabel',
3329 'classes' => [ $this->missingSummary ?
'mw-summarymissed' :
'mw-summary' ],
3341 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3342 $summaryClass = $this->missingSummary ?
'mw-summarymissed' :
'mw-summary';
3343 if ( $isSubjectPreview ) {
3344 if ( $this->nosummary ) {
3347 } elseif ( !$this->mShowSummaryField ) {
3351 $labelText = $this->context->msg( $isSubjectPreview ?
'subject' :
'summary' )->parse();
3355 [
'class' => $summaryClass ]
3369 if ( !
$summary || ( !$this->preview && !$this->diff ) ) {
3373 if ( $isSubjectPreview ) {
3374 $summary = $this->context->msg(
'newsectionsummary' )
3375 ->rawParams( MediaWikiServices::getInstance()->getParser()
3377 ->inContentLanguage()->text();
3380 $message = $isSubjectPreview ?
'subject-preview' :
'summary-preview';
3382 $summary = $this->context->msg( $message )->parse()
3388 $out = $this->context->getOutput();
3389 $out->addHTML( Html::hidden(
'wpSection', $this->section ) );
3390 $out->addHTML( Html::hidden(
'wpStarttime', $this->starttime ) );
3391 $out->addHTML( Html::hidden(
'wpEdittime', $this->edittime ) );
3392 $out->addHTML( Html::hidden(
'editRevId', $this->editRevId ) );
3393 $out->addHTML( Html::hidden(
'wpScrolltop', $this->scrolltop, [
'id' =>
'wpScrolltop' ] ) );
3409 $this->context->getOutput()->addHTML(
3411 Html::hidden(
"wpEditToken", $this->context->getUser()->getEditToken() ) .
3436 protected function showTextbox1( $customAttribs =
null, $textoverride =
null ) {
3438 $attribs = [
'style' =>
'display:none;' ];
3441 $classes = $builder->getTextboxProtectionCSSClasses( $this->
getTitle() );
3443 # Is an old revision being edited?
3444 if ( $this->isOldRev ) {
3445 $classes[] =
'mw-textarea-oldrev';
3448 $attribs = [
'tabindex' => 1 ];
3450 if ( is_array( $customAttribs ) ) {
3451 $attribs += $customAttribs;
3454 $attribs = $builder->mergeClassesIntoAttributes( $classes, $attribs );
3458 $textoverride ?? $this->textbox1,
3465 $this->
showTextbox( $this->textbox2,
'wpTextbox2', [
'tabindex' => 6,
'readonly' ] );
3470 $attribs = $builder->buildTextboxAttribs(
3473 $this->context->getUser(),
3477 $this->context->getOutput()->addHTML(
3478 Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
3485 $classes[] =
'ontop';
3488 $attribs = [
'id' =>
'wikiPreview',
'class' => implode(
' ', $classes ) ];
3490 if ( $this->formtype !=
'preview' ) {
3491 $attribs[
'style'] =
'display: none;';
3494 $out = $this->context->getOutput();
3497 if ( $this->formtype ==
'preview' ) {
3501 $pageViewLang = $this->mTitle->getPageViewLanguage();
3502 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3503 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3504 $out->addHTML( Html::rawElement(
'div', $attribs ) );
3507 $out->addHTML(
'</div>' );
3509 if ( $this->formtype ==
'diff' ) {
3513 $msg = $this->context->msg(
3514 'content-failed-to-parse',
3515 $this->contentModel,
3516 $this->contentFormat,
3519 $out->wrapWikiTextAsInterface(
'error', $msg->plain() );
3532 $this->mArticle->openShowCategory();
3534 # This hook seems slightly odd here, but makes things more
3535 # consistent for extensions.
3536 $out = $this->context->getOutput();
3537 Hooks::run(
'OutputPageBeforeHTML', [ &$out, &$text ] );
3538 $out->addHTML( $text );
3540 $this->mArticle->closeShowCategory();
3552 $oldtitlemsg =
'currentrev';
3553 # if message does not exist, show diff against the preloaded default
3554 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3555 $oldtext = $this->mTitle->getDefaultMessageText();
3556 if ( $oldtext !==
false ) {
3557 $oldtitlemsg =
'defaultmessagetext';
3567 if ( $this->editRevId !==
null ) {
3568 $newContent = $this->page->replaceSectionAtRev(
3569 $this->section, $textboxContent, $this->summary, $this->editRevId
3572 $newContent = $this->page->replaceSectionContent(
3573 $this->section, $textboxContent, $this->summary, $this->edittime
3577 if ( $newContent ) {
3578 Hooks::run(
'EditPageGetDiffContent', [ $this, &$newContent ] );
3580 $user = $this->context->getUser();
3582 MediaWikiServices::getInstance()->getContentLanguage() );
3583 $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3586 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3587 $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3588 $newtitle = $this->context->msg(
'yourtext' )->parse();
3590 if ( !$oldContent ) {
3591 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3594 if ( !$newContent ) {
3595 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3598 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3599 $de->setContent( $oldContent, $newContent );
3601 $difftext = $de->getDiff( $oldtitle, $newtitle );
3602 $de->showDiffStyle();
3607 $this->context->getOutput()->addHTML(
'<div id="wikiDiff">' . $difftext .
'</div>' );
3614 $msg =
'editpage-head-copy-warn';
3615 if ( !$this->context->msg( $msg )->isDisabled() ) {
3616 $this->context->getOutput()->wrapWikiMsg(
"<div class='editpage-head-copywarn'>\n$1\n</div>",
3617 'editpage-head-copy-warn' );
3630 $msg =
'editpage-tos-summary';
3631 Hooks::run(
'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3632 if ( !$this->context->msg( $msg )->isDisabled() ) {
3633 $out = $this->context->getOutput();
3634 $out->addHTML(
'<div class="mw-tos-summary">' );
3635 $out->addWikiMsg( $msg );
3636 $out->addHTML(
'</div>' );
3645 $this->context->getOutput()->addHTML(
'<div class="mw-editTools">' .
3646 $this->context->msg(
'edittools' )->inContentLanguage()->parse() .
3671 $copywarnMsg = [
'copyrightwarning',
3672 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]',
3675 $copywarnMsg = [
'copyrightwarning2',
3676 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]' ];
3683 $msg->inLanguage( $langcode );
3685 return "<div id=\"editpage-copywarn\">\n" .
3686 $msg->$format() .
"\n</div>";
3703 $limitReport = Html::rawElement(
'div', [
'class' =>
'mw-limitReportExplanation' ],
3704 wfMessage(
'limitreport-title' )->parseAsBlock()
3708 $limitReport .= Html::openElement(
'div', [
'class' =>
'preview-limit-report-wrapper' ] );
3710 $limitReport .= Html::openElement(
'table', [
3711 'class' =>
'preview-limit-report wikitable'
3713 Html::openElement(
'tbody' );
3715 foreach (
$output->getLimitReportData() as $key => $value ) {
3717 [ $key, &$value, &$limitReport,
true,
true ]
3720 $valueMsg =
wfMessage( [
"$key-value-html",
"$key-value" ] );
3721 if ( !$valueMsg->exists() ) {
3724 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3725 $limitReport .= Html::openElement(
'tr' ) .
3726 Html::rawElement(
'th',
null, $keyMsg->parse() ) .
3727 Html::rawElement(
'td',
null,
3728 $wgLang->formatNum( $valueMsg->params( $value )->parse() )
3730 Html::closeElement(
'tr' );
3735 $limitReport .= Html::closeElement(
'tbody' ) .
3736 Html::closeElement(
'table' ) .
3737 Html::closeElement(
'div' );
3739 return $limitReport;
3743 $out = $this->context->getOutput();
3744 $out->addHTML(
"<div class='editOptions'>\n" );
3746 if ( $this->section !=
'new' ) {
3753 [
'minor' => $this->minoredit,
'watch' => $this->watchthis ]
3755 $checkboxesHTML =
new OOUI\HorizontalLayout( [
'items' => $checkboxes ] );
3757 $out->addHTML(
"<div class='editCheckboxes'>" . $checkboxesHTML .
"</div>\n" );
3760 $out->addWikiTextAsInterface( $this->
getCopywarn() );
3761 $out->addHTML( $this->editFormTextAfterWarn );
3763 $out->addHTML(
"<div class='editButtons'>\n" );
3764 $out->addHTML( implode(
"\n", $this->
getEditButtons( $tabindex ) ) .
"\n" );
3768 $message = $this->context->msg(
'edithelppage' )->inContentLanguage()->text();
3772 $this->context->msg(
'edithelp' )->text(),
3773 [
'target' =>
'helpwindow',
'href' => $edithelpurl ],
3776 $this->context->msg(
'word-separator' )->escaped() .
3777 $this->context->msg(
'newwindow' )->parse();
3779 $out->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3780 $out->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3781 $out->addHTML(
"</div><!-- editButtons -->\n" );
3783 Hooks::run(
'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
3785 $out->addHTML(
"</div><!-- editOptions -->\n" );
3793 $out = $this->context->getOutput();
3796 if (
Hooks::run(
'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
3812 if ( !$this->isConflict && $this->oldid > 0 ) {
3815 $cancelParams[
'redirect'] =
'no';
3818 return new OOUI\ButtonWidget( [
3819 'id' =>
'mw-editform-cancel',
3821 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
'cancel' )->parse() ),
3823 'infusable' =>
true,
3824 'flags' =>
'destructive',
3838 return $title->getLocalURL( [
'action' => $this->action ] );
3849 if ( $this->deletedSinceEdit !==
null ) {
3853 $this->deletedSinceEdit =
false;
3855 if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3857 if ( $this->lastDelete ) {
3858 $deleteTime =
wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3859 if ( $deleteTime > $this->starttime ) {
3860 $this->deletedSinceEdit =
true;
3877 $data =
$dbr->selectRow(
3878 array_merge( [
'logging' ], $commentQuery[
'tables'], $actorQuery[
'tables'], [
'user' ] ),
3888 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
3890 'log_namespace' => $this->mTitle->getNamespace(),
3891 'log_title' => $this->mTitle->getDBkey(),
3892 'log_type' =>
'delete',
3893 'log_action' =>
'delete',
3896 [
'LIMIT' => 1,
'ORDER BY' =>
'log_timestamp DESC' ],
3898 'user' => [
'JOIN',
'user_id=' . $actorQuery[
'fields'][
'log_user'] ],
3899 ] + $commentQuery[
'joins'] + $actorQuery[
'joins']
3902 if ( is_object( $data ) ) {
3904 $data->user_name = $this->context->msg(
'rev-deleted-user' )->escaped();
3908 $data->log_comment_text = $this->context->msg(
'rev-deleted-comment' )->escaped();
3909 $data->log_comment_data =
null;
3922 $out = $this->context->getOutput();
3923 $config = $this->context->getConfig();
3929 if ( $this->textbox1 !==
'' ) {
3933 $parsedNote = Html::rawElement(
'div', [
'class' =>
'previewnote' ],
3934 $out->parseAsInterface(
3935 $this->context->msg(
'session_fail_preview_html' )->plain()
3949 'AlternateEditPreview',
3950 [ $this, &
$content, &$previewHTML, &$this->mParserOutput ] )
3952 return $previewHTML;
3955 # provide a anchor link to the editform
3956 $continueEditing =
'<span class="mw-continue-editing">' .
3957 '[[#' . self::EDITFORM_ID .
'|' .
3958 $this->context->getLanguage()->getArrow() .
' ' .
3959 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
3960 if ( $this->mTriedSave && !$this->mTokenOk ) {
3961 if ( $this->mTokenOkExceptSuffix ) {
3962 $note = $this->context->msg(
'token_suffix_mismatch' )->plain();
3965 $note = $this->context->msg(
'session_fail_preview' )->plain();
3968 } elseif ( $this->incompleteForm ) {
3969 $note = $this->context->msg(
'edit_form_incomplete' )->plain();
3970 if ( $this->mTriedSave ) {
3974 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
3977 # don't parse non-wikitext pages, show message about preview
3978 if ( $this->mTitle->isUserConfigPage() || $this->mTitle->isSiteConfigPage() ) {
3979 if ( $this->mTitle->isUserConfigPage() ) {
3981 } elseif ( $this->mTitle->isSiteConfigPage() ) {
3989 if ( $level ===
'user' && !$config->get(
'AllowUserCss' ) ) {
3994 if ( $level ===
'user' ) {
3999 if ( $level ===
'user' && !$config->get(
'AllowUserJs' ) ) {
4006 # Used messages to make sure grep find them:
4007 # Messages: usercsspreview, userjsonpreview, userjspreview,
4008 # sitecsspreview, sitejsonpreview, sitejspreview
4009 if ( $level && $format ) {
4010 $note =
"<div id='mw-{$level}{$format}preview'>" .
4011 $this->context->msg(
"{$level}{$format}preview" )->plain() .
4012 ' ' . $continueEditing .
"</div>";
4016 # If we're adding a comment, we need to show the
4017 # summary as the headline
4018 if ( $this->section ===
"new" && $this->summary !==
"" ) {
4023 Hooks::run(
'EditPageGetPreviewContent', $hook_args );
4026 $parserOutput = $parserResult[
'parserOutput'];
4027 $previewHTML = $parserResult[
'html'];
4028 $this->mParserOutput = $parserOutput;
4029 $out->addParserOutputMetadata( $parserOutput );
4030 if ( $out->userCanPreview() ) {
4034 if ( count( $parserOutput->getWarnings() ) ) {
4035 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
4039 $m = $this->context->msg(
4040 'content-failed-to-parse',
4041 $this->contentModel,
4042 $this->contentFormat,
4045 $note .=
"\n\n" . $m->plain(); # gets parsed down below
4049 if ( $this->isConflict ) {
4050 $conflict = Html::rawElement(
4051 'div', [
'id' =>
'mw-previewconflict',
'class' =>
'warningbox' ],
4052 $this->context->msg(
'previewconflict' )->escaped()
4058 $previewhead = Html::rawElement(
4059 'div', [
'class' =>
'previewnote' ],
4061 'h2', [
'id' =>
'mw-previewheader' ],
4062 $this->context->msg(
'preview' )->escaped()
4064 Html::rawElement(
'div', [
'class' =>
'warningbox' ],
4065 $out->parseAsInterface( $note )
4069 $pageViewLang = $this->mTitle->getPageViewLanguage();
4070 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
4071 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
4072 $previewHTML = Html::rawElement(
'div', $attribs, $previewHTML );
4078 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
4079 $stats->increment(
'edit.failures.' . $failureType );
4087 $parserOptions = $this->page->makeParserOptions( $this->context );
4088 $parserOptions->setIsPreview(
true );
4089 $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !==
'' );
4090 $parserOptions->enableLimitReport();
4097 return $parserOptions;
4110 $user = $this->context->getUser();
4118 $pstContent =
$content->preSaveTransform( $this->mTitle, $user, $parserOptions );
4119 $scopedCallback = $parserOptions->setupFakeRevision( $this->mTitle, $pstContent, $user );
4120 $parserOutput = $pstContent->getParserOutput( $this->mTitle,
null, $parserOptions );
4121 ScopedCallback::consume( $scopedCallback );
4123 'parserOutput' => $parserOutput,
4124 'html' => $parserOutput->getText( [
4125 'enableSectionEditLinks' =>
false
4134 if ( $this->preview || $this->section !=
'' ) {
4136 if ( !isset( $this->mParserOutput ) ) {
4139 foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
4140 foreach ( array_keys( $template ) as $dbk ) {
4146 return $this->mTitle->getTemplateLinksFrom();
4156 $startingToolbar =
'<div id="toolbar"></div>';
4157 $toolbar = $startingToolbar;
4159 if ( !
Hooks::run(
'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4163 return ( $toolbar === $startingToolbar ) ? null : $toolbar;
4187 $user = $this->context->getUser();
4189 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
4190 if ( !$this->isNew && $permissionManager->userHasRight( $user,
'minoredit' ) ) {
4191 $checkboxes[
'wpMinoredit'] = [
4192 'id' =>
'wpMinoredit',
4193 'label-message' =>
'minoredit',
4195 'tooltip' =>
'minoredit',
4196 'label-id' =>
'mw-editpage-minoredit',
4197 'legacy-name' =>
'minor',
4198 'default' => $checked[
'minor'],
4202 if ( $user->isLoggedIn() ) {
4203 $checkboxes[
'wpWatchthis'] = [
4204 'id' =>
'wpWatchthis',
4205 'label-message' =>
'watchthis',
4207 'tooltip' =>
'watch',
4208 'label-id' =>
'mw-editpage-watch',
4209 'legacy-name' =>
'watch',
4210 'default' => $checked[
'watch'],
4215 Hooks::run(
'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4234 foreach ( $checkboxesDef as $name => $options ) {
4235 $legacyName = $options[
'legacy-name'] ?? $name;
4239 if ( isset( $options[
'tooltip'] ) ) {
4240 $accesskey = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4243 if ( isset( $options[
'title-message'] ) ) {
4244 $title = $this->context->msg( $options[
'title-message'] )->text();
4247 $checkboxes[ $legacyName ] =
new OOUI\FieldLayout(
4248 new OOUI\CheckboxInputWidget( [
4249 'tabIndex' => ++$tabindex,
4250 'accessKey' => $accesskey,
4251 'id' => $options[
'id'] .
'Widget',
4252 'inputId' => $options[
'id'],
4254 'selected' => $options[
'default'],
4255 'infusable' =>
true,
4258 'align' =>
'inline',
4259 'label' =>
new OOUI\HtmlSnippet( $this->context->msg( $options[
'label-message'] )->parse() ),
4261 'id' => $options[
'label-id'] ??
null,
4277 $this->context->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4280 $newPage = !$this->mTitle->exists();
4282 if ( $labelAsPublish ) {
4283 $buttonLabelKey = $newPage ?
'publishpage' :
'publishchanges';
4285 $buttonLabelKey = $newPage ?
'savearticle' :
'savechanges';
4288 return $buttonLabelKey;
4303 $this->context->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4306 $buttonTooltip = $labelAsPublish ?
'publish' :
'save';
4308 $buttons[
'save'] =
new OOUI\ButtonInputWidget( [
4310 'tabIndex' => ++$tabindex,
4311 'id' =>
'wpSaveWidget',
4312 'inputId' =>
'wpSave',
4314 'useInputTag' =>
true,
4315 'flags' => [
'progressive',
'primary' ],
4316 'label' => $buttonLabel,
4317 'infusable' =>
true,
4325 $buttons[
'preview'] =
new OOUI\ButtonInputWidget( [
4326 'name' =>
'wpPreview',
4327 'tabIndex' => ++$tabindex,
4328 'id' =>
'wpPreviewWidget',
4329 'inputId' =>
'wpPreview',
4331 'useInputTag' =>
true,
4332 'label' => $this->context->msg(
'showpreview' )->text(),
4333 'infusable' =>
true,
4341 $buttons[
'diff'] =
new OOUI\ButtonInputWidget( [
4343 'tabIndex' => ++$tabindex,
4344 'id' =>
'wpDiffWidget',
4345 'inputId' =>
'wpDiff',
4347 'useInputTag' =>
true,
4348 'label' => $this->context->msg(
'showdiff' )->text(),
4349 'infusable' =>
true,
4359 Hooks::run(
'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &$tabindex ] );
4369 $out = $this->context->getOutput();
4370 $out->prepareErrorPage( $this->context->msg(
'nosuchsectiontitle' ) );
4372 $res = $this->context->msg(
'nosuchsectiontext', $this->section )->parseAsBlock();
4377 $out->addHTML(
$res );
4379 $out->returnToMain(
false, $this->mTitle );
4390 if ( is_array( $match ) ) {
4391 $match = $this->context->getLanguage()->listToText( $match );
4393 $out = $this->context->getOutput();
4394 $out->prepareErrorPage( $this->context->msg(
'spamprotectiontitle' ) );
4396 $out->addHTML(
'<div id="spamprotected">' );
4397 $out->addWikiMsg(
'spamprotectiontext' );
4401 $out->addHTML(
'</div>' );
4403 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
4406 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
4409 $out->addReturnTo( $this->
getContextTitle(), [
'action' =>
'edit' ] );
4416 $out = $this->context->getOutput();
4417 $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4418 if ( count( $editNotices ) ) {
4419 $out->addHTML( implode(
"\n", $editNotices ) );
4421 $msg = $this->context->msg(
'editnotice-notext' );
4422 if ( !$msg->isDisabled() ) {
4424 '<div class="mw-editnotice-notext">'
4425 . $msg->parseAsBlock()
4436 if ( $this->mTitle->isTalkPage() ) {
4437 $this->context->getOutput()->addWikiMsg(
'talkpagetext' );
4445 if ( $this->contentLength ===
false ) {
4446 $this->contentLength = strlen( $this->textbox1 );
4449 $out = $this->context->getOutput();
4450 $lang = $this->context->getLanguage();
4451 $maxArticleSize = $this->context->getConfig()->get(
'MaxArticleSize' );
4452 if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4453 $out->wrapWikiMsg(
"<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4456 $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4457 $lang->formatNum( $maxArticleSize )
4460 } elseif ( !$this->context->msg(
'longpage-hint' )->isDisabled() ) {
4461 $out->wrapWikiMsg(
"<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4464 $lang->formatSize( strlen( $this->textbox1 ) ),
4465 strlen( $this->textbox1 )
4475 $out = $this->context->getOutput();
4476 if ( $this->mTitle->isProtected(
'edit' ) &&
4477 MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
4481 # Is the title semi-protected?
4482 if ( $this->mTitle->isSemiProtected() ) {
4483 $noticeMsg =
'semiprotectedpagewarning';
4485 # Then it must be protected based on static groups (regular)
4486 $noticeMsg =
'protectedpagewarning';
4489 [
'lim' => 1,
'msgKey' => [ $noticeMsg ] ] );
4491 if ( $this->mTitle->isCascadeProtected() ) {
4492 # Is this page under cascading protection from some source pages?
4494 list( $cascadeSources, ) = $this->mTitle->getCascadeProtectionSources();
4495 $notice =
"<div class='mw-cascadeprotectedwarning'>\n$1\n";
4496 $cascadeSourcesCount = count( $cascadeSources );
4497 if ( $cascadeSourcesCount > 0 ) {
4498 # Explain, and list the titles responsible
4499 foreach ( $cascadeSources as
$page ) {
4500 $notice .=
'* [[:' .
$page->getPrefixedText() .
"]]\n";
4503 $notice .=
'</div>';
4504 $out->wrapWikiMsg( $notice, [
'cascadeprotectedwarning', $cascadeSourcesCount ] );
4506 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions(
'create' ) ) {
4509 'showIfEmpty' =>
false,
4510 'msgKey' => [
'titleprotectedwarning' ],
4511 'wrap' =>
"<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4534 $name, $customAttribs, $user, $this->mTitle
4559 $userAgent = $this->context->getRequest()->getHeader(
'User-Agent' );
4560 $parser = MediaWikiServices::getInstance()->getParser();
4561 if ( $userAgent && preg_match(
'/MSIE|Edge/', $userAgent ) ) {
4563 return $parser->guessLegacySectionNameFromWikiText( $text );
4566 $name = $parser->guessSectionNameFromWikiText( $text );
4569 return '#' . urlencode( mb_substr( $name, 1 ) );
4579 $this->editConflictHelperFactory = $factory;
4580 $this->editConflictHelper =
null;
4587 if ( !$this->editConflictHelper ) {
4588 $this->editConflictHelper = call_user_func(
4589 $this->editConflictHelperFactory,
4605 MediaWikiServices::getInstance()->getStatsdDataFactory(),