25 use Wikimedia\ScopedCallback;
403 # Placeholders for text injection by hooks (must be HTML)
404 # extensions should take care to _append_ to the present value
455 $this->mTitle =
$article->getTitle();
456 $this->context =
$article->getContext();
458 $this->contentModel = $this->mTitle->getContentModel();
461 $this->contentFormat =
$handler->getDefaultFormat();
493 $this->mContextTitle =
$title;
504 if ( is_null( $this->mContextTitle ) ) {
507 __METHOD__ .
' called by ' .
wfGetAllCallers( 5 ) .
' with no title set.'
533 return $this->enableApiEditOverride ===
true ||
544 $this->enableApiEditOverride = $enableOverride;
568 if ( !
Hooks::run(
'AlternateEdit', [ $this ] ) ) {
572 wfDebug( __METHOD__ .
": enter\n" );
574 $request = $this->context->getRequest();
576 if (
$request->getBool(
'redlink' ) && $this->mTitle->exists() ) {
577 $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
582 $this->firsttime =
false;
587 $this->preview =
true;
591 $this->formtype =
'save';
592 } elseif ( $this->preview ) {
593 $this->formtype =
'preview';
594 } elseif ( $this->
diff ) {
595 $this->formtype =
'diff';
596 }
else { #
First time through
597 $this->firsttime =
true;
599 $this->formtype =
'preview';
601 $this->formtype =
'initial';
607 wfDebug( __METHOD__ .
": User can't edit\n" );
611 $this->context->getUser()->spreadAnyEditBlock();
619 $revision = $this->mArticle->getRevisionFetched();
626 if ( $this->undidRev ) {
628 $prevRev = $undidRevObj ? $undidRevObj->getPrevious() :
null;
630 if ( !$this->undidRev
637 'contentmodelediterror',
638 $revision->getContentModel(),
646 $this->isConflict =
false;
650 $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
651 $this->isCssSubpage = $this->mTitle->isCssSubpage();
652 $this->isJsSubpage = $this->mTitle->isJsSubpage();
655 # Show applicable editing introductions
656 if ( $this->formtype ==
'initial' || $this->firsttime ) {
660 # Attempt submission here. This will check for edit conflicts,
661 # and redundantly check for locked database, blocked IPs, etc.
662 # that edit() already checked just in case someone tries to sneak
663 # in the back door with a hand-edited submission URL.
665 if (
'save' == $this->formtype ) {
666 $resultDetails =
null;
673 # First time through: get contents, set time for conflict
675 if (
'initial' == $this->formtype || $this->firsttime ) {
681 if ( !$this->mTitle->getArticleID() ) {
682 Hooks::run(
'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
684 Hooks::run(
'EditFormInitialText', [ $this ] );
697 $user = $this->context->getUser();
698 $permErrors = $this->mTitle->getUserPermissionsErrors(
'edit',
$user, $rigor );
699 # Can this title be created?
700 if ( !$this->mTitle->exists() ) {
701 $permErrors = array_merge(
704 $this->mTitle->getUserPermissionsErrors(
'create',
$user, $rigor ),
709 # Ignore some permissions errors when a user is just previewing/viewing diffs
711 foreach ( $permErrors
as $error ) {
712 if ( ( $this->preview || $this->
diff )
714 $error[0] ==
'blockedtext' ||
715 $error[0] ==
'autoblockedtext' ||
716 $error[0] ==
'systemblockedtext'
741 $out = $this->context->getOutput();
742 if ( $this->context->getRequest()->getBool(
'redlink' ) ) {
746 $out->redirect( $this->mTitle->getFullURL() );
752 # Use the normal message if there's nothing to display
753 if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
754 $action = $this->mTitle->exists() ?
'edit' :
755 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
761 $out->formatPermissionsErrorMessage( $permErrors,
'edit' )
771 $out = $this->context->getOutput();
772 Hooks::run(
'EditPage::showReadOnlyForm:initial', [ $this, &
$out ] );
774 $out->setRobotPolicy(
'noindex,nofollow' );
775 $out->setPageTitle( $this->context->msg(
777 $this->getContextTitle()->getPrefixedText()
780 $out->addHTML( $this->editFormPageTop );
781 $out->addHTML( $this->editFormTextTop );
783 if ( $errorMessage !==
'' ) {
784 $out->addWikiText( $errorMessage );
785 $out->addHTML(
"<hr />\n" );
788 # If the user made changes, preserve them when showing the markup
789 # (This happens when a user is blocked during edit, for instance)
790 if ( !$this->firsttime ) {
792 $out->addWikiMsg(
'viewyourtext' );
797 # Serialize using the default format if the content model is not supported
798 # (e.g. for an old revision with a different model)
801 $out->addWikiMsg(
'viewsourcetext' );
804 $out->addHTML( $this->editFormTextBeforeContent );
805 $this->
showTextbox( $text,
'wpTextbox1', [
'readonly' ] );
806 $out->addHTML( $this->editFormTextAfterContent );
810 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
812 $out->addHTML( $this->editFormTextBottom );
813 if ( $this->mTitle->exists() ) {
814 $out->returnToMain(
null, $this->mTitle );
824 $previewOnOpenNamespaces = $this->context->getConfig()->get(
'PreviewOnOpenNamespaces' );
825 $request = $this->context->getRequest();
826 if (
$request->getVal(
'preview' ) ==
'yes' ) {
829 } elseif (
$request->getVal(
'preview' ) ==
'no' ) {
832 } elseif ( $this->section ==
'new' ) {
835 } elseif ( (
$request->getVal(
'preload' ) !==
null || $this->mTitle->exists() )
836 && $this->context->getUser()->getOption(
'previewonfirst' )
840 } elseif ( !$this->mTitle->exists()
841 && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
842 && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
858 if ( $this->mTitle->isCssJsSubpage() ) {
859 $name = $this->mTitle->getSkinFromCssJsSubpage();
860 $skins = array_merge(
864 return !in_array(
$name, $skins )
865 && in_array( strtolower(
$name ), $skins );
880 return $contentHandler->supportsSections();
889 # Section edit can come from either the form or a link
890 $this->section =
$request->getVal(
'wpSection',
$request->getVal(
'section' ) );
893 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
896 $this->isNew = !$this->mTitle->exists() || $this->section ==
'new';
899 # These fields need to be checked for encoding.
900 # Also remove trailing whitespace, but don't remove _initial_
901 # whitespace from the text boxes. This may be significant formatting.
902 $this->textbox1 = rtrim(
$request->getText(
'wpTextbox1' ) );
903 if ( !
$request->getCheck(
'wpTextbox2' ) ) {
913 $this->unicodeCheck =
$request->getText(
'wpUnicodeCheck' );
915 $this->summary =
$request->getText(
'wpSummary' );
917 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
918 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
920 $this->summary = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->summary );
922 # Treat sectiontitle the same way as summary.
923 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
924 # currently doing double duty as both edit summary and section title. Right now this
925 # is just to allow API edits to work around this limitation, but this should be
926 # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
927 $this->sectiontitle =
$request->getText(
'wpSectionTitle' );
928 $this->sectiontitle = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->sectiontitle );
930 $this->edittime =
$request->getVal(
'wpEdittime' );
931 $this->editRevId =
$request->getIntOrNull(
'editRevId' );
932 $this->starttime =
$request->getVal(
'wpStarttime' );
939 $this->scrolltop =
$request->getIntOrNull(
'wpScrolltop' );
941 if ( $this->textbox1 ===
'' &&
$request->getVal(
'wpTextbox1' ) === null ) {
945 $this->incompleteForm =
true;
953 $this->incompleteForm = ( !
$request->getVal(
'wpUltimateParam' )
954 && is_null( $this->edittime ) );
956 if ( $this->incompleteForm ) {
957 # If the form is incomplete, force to preview.
958 wfDebug( __METHOD__ .
": Form data appears to be incomplete\n" );
959 wfDebug(
"POST DATA: " . var_export( $_POST,
true ) .
"\n" );
960 $this->preview =
true;
962 $this->preview =
$request->getCheck(
'wpPreview' );
970 # Some browsers will not report any submit button
971 # if the user hits enter in the comment box.
972 # The unmarked state will be assumed to be a save,
973 # if the form seems otherwise complete.
974 wfDebug( __METHOD__ .
": Passed token check.\n" );
975 } elseif ( $this->
diff ) {
976 # Failed token check, but only requested "Show Changes".
977 wfDebug( __METHOD__ .
": Failed token check; Show Changes requested.\n" );
979 # Page might be a hack attempt posted from
980 # an external site. Preview instead of saving.
981 wfDebug( __METHOD__ .
": Failed token check; forcing preview\n" );
982 $this->preview =
true;
986 if ( !preg_match(
'/^\d{14}$/', $this->edittime ) ) {
987 $this->edittime =
null;
990 if ( !preg_match(
'/^\d{14}$/', $this->starttime ) ) {
991 $this->starttime =
null;
994 $this->recreate =
$request->getCheck(
'wpRecreate' );
996 $this->minoredit =
$request->getCheck(
'wpMinoredit' );
997 $this->watchthis =
$request->getCheck(
'wpWatchthis' );
999 $user = $this->context->getUser();
1000 # Don't force edit summaries when a user is editing their own user or talk page
1001 if ( ( $this->mTitle->mNamespace ==
NS_USER || $this->mTitle->mNamespace ==
NS_USER_TALK )
1002 && $this->mTitle->getText() ==
$user->getName()
1004 $this->allowBlankSummary =
true;
1006 $this->allowBlankSummary =
$request->getBool(
'wpIgnoreBlankSummary' )
1007 || !
$user->getOption(
'forceeditsummary' );
1010 $this->autoSumm =
$request->getText(
'wpAutoSummary' );
1012 $this->allowBlankArticle =
$request->getBool(
'wpIgnoreBlankArticle' );
1013 $this->allowSelfRedirect =
$request->getBool(
'wpIgnoreSelfRedirect' );
1017 $this->changeTags = [];
1019 $this->changeTags = array_filter( array_map(
'trim', explode(
',',
1023 # Not a posted form? Start with nothing.
1024 wfDebug( __METHOD__ .
": Not a posted form.\n" );
1025 $this->textbox1 =
'';
1026 $this->summary =
'';
1027 $this->sectiontitle =
'';
1028 $this->edittime =
'';
1029 $this->editRevId =
null;
1031 $this->
edit =
false;
1032 $this->preview =
false;
1033 $this->
save =
false;
1034 $this->
diff =
false;
1035 $this->minoredit =
false;
1037 $this->watchthis =
$request->getBool(
'watchthis',
false );
1038 $this->recreate =
false;
1042 if ( $this->section ==
'new' &&
$request->getVal(
'preloadtitle' ) ) {
1043 $this->sectiontitle =
$request->getVal(
'preloadtitle' );
1045 $this->summary =
$request->getVal(
'preloadtitle' );
1046 } elseif ( $this->section !=
'new' &&
$request->getVal(
'summary' ) ) {
1047 $this->summary =
$request->getText(
'summary' );
1048 if ( $this->summary !==
'' ) {
1049 $this->hasPresetSummary =
true;
1053 if (
$request->getVal(
'minor' ) ) {
1054 $this->minoredit =
true;
1058 $this->oldid =
$request->getInt(
'oldid' );
1059 $this->parentRevId =
$request->getInt(
'parentRevId' );
1061 $this->bot =
$request->getBool(
'bot',
true );
1062 $this->nosummary =
$request->getBool(
'nosummary' );
1065 $this->contentModel =
$request->getText(
'model', $this->contentModel );
1067 $this->contentFormat =
$request->getText(
'format', $this->contentFormat );
1073 'editpage-invalidcontentmodel-title',
1074 'editpage-invalidcontentmodel-text',
1079 if ( !
$handler->isSupportedFormat( $this->contentFormat ) ) {
1081 'editpage-notsupportedcontentformat-title',
1082 'editpage-notsupportedcontentformat-text',
1096 $this->editintro =
$request->getText(
'editintro',
1098 $this->section ===
'new' ?
'MediaWiki:addsection-editintro' :
'' );
1123 $this->edittime = $this->
page->getTimestamp();
1124 $this->editRevId = $this->
page->getLatest();
1127 if ( $content ===
false ) {
1130 $this->textbox1 = $this->
toEditText( $content );
1132 $user = $this->context->getUser();
1134 # Sort out the "watch" checkbox
1135 if (
$user->getOption(
'watchdefault' ) ) {
1137 $this->watchthis =
true;
1138 } elseif (
$user->getOption(
'watchcreations' ) && !$this->mTitle->exists() ) {
1140 $this->watchthis =
true;
1141 } elseif (
$user->isWatched( $this->mTitle ) ) {
1143 $this->watchthis =
true;
1146 $this->minoredit =
true;
1148 if ( $this->textbox1 ===
false ) {
1166 $user = $this->context->getUser();
1167 $request = $this->context->getRequest();
1170 if ( !$this->mTitle->exists() || $this->section ==
'new' ) {
1171 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && $this->section !=
'new' ) {
1172 # If this is a system message, get the default text.
1173 $msg = $this->mTitle->getDefaultMessageText();
1177 if ( $content ===
false ) {
1178 # If requested, preload some text.
1179 $preload =
$request->getVal(
'preload',
1181 $this->section ===
'new' ?
'MediaWiki:addsection-preload' :
'' );
1188 if ( $this->section !=
'' ) {
1191 $content = $orig ? $orig->getSection( $this->section ) :
null;
1194 $content = $def_content;
1197 $undoafter =
$request->getInt(
'undoafter' );
1198 $undo =
$request->getInt(
'undo' );
1200 if ( $undo > 0 && $undoafter > 0 ) {
1204 # Sanity check, make sure it's the right page,
1205 # the revisions exist and they were not deleted.
1206 # Otherwise, $content will be left as-is.
1207 if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1211 $content = $this->
page->getUndoContent( $undorev, $oldrev );
1213 if ( $content ===
false ) {
1214 # Warn the user that something went wrong
1215 $undoMsg =
'failure';
1219 $newContent = $content->preSaveTransform( $this->mTitle,
$user, $popts );
1220 if ( $newContent->getModel() !== $oldContent->getModel() ) {
1225 $this->contentModel = $newContent->getModel();
1226 $this->contentFormat = $oldrev->getContentFormat();
1229 if ( $newContent->equals( $oldContent ) ) {
1230 # Tell the user that the undo results in no change,
1231 # i.e. the revisions were already undone.
1232 $undoMsg =
'nochange';
1235 # Inform the user of our success and set an automatic edit summary
1236 $undoMsg =
'success';
1238 # If we just undid one rev, use an autosummary
1239 $firstrev = $oldrev->getNext();
1240 if ( $firstrev && $firstrev->getId() == $undo ) {
1241 $userText = $undorev->getUserText();
1242 if ( $userText ===
'' ) {
1243 $undoSummary = $this->context->msg(
1244 'undo-summary-username-hidden',
1246 )->inContentLanguage()->text();
1248 $undoSummary = $this->context->msg(
1252 )->inContentLanguage()->text();
1254 if ( $this->summary ===
'' ) {
1255 $this->summary = $undoSummary;
1257 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1260 $this->undidRev = $undo;
1262 $this->formtype =
'diff';
1272 $out = $this->context->getOutput();
1274 $class = ( $undoMsg ==
'success' ?
'' :
'error ' ) .
"mw-undo-{$undoMsg}";
1275 $this->editFormPageTop .=
$out->parse(
"<div class=\"{$class}\">" .
1276 $this->context->msg(
'undo-' . $undoMsg )->plain() .
'</div>',
true,
true );
1279 if ( $content ===
false ) {
1304 if ( $this->section ==
'new' ) {
1307 $revision = $this->mArticle->getRevisionFetched();
1308 if ( $revision ===
null ) {
1310 return $handler->makeEmptyContent();
1329 if ( $this->parentRevId ) {
1332 return $this->mArticle->getRevIdFetched();
1348 if ( $content ===
false || $content ===
null ) {
1350 return $handler->makeEmptyContent();
1351 } elseif ( !$this->undidRev ) {
1356 $logger = LoggerFactory::getInstance(
'editpage' );
1357 if ( $this->contentModel !==
$rev->getContentModel() ) {
1358 $logger->warning(
"Overriding content model from current edit {prev} to {new}", [
1359 'prev' => $this->contentModel,
1360 'new' =>
$rev->getContentModel(),
1361 'title' => $this->
getTitle()->getPrefixedDBkey(),
1362 'method' => __METHOD__
1364 $this->contentModel =
$rev->getContentModel();
1369 if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1370 $logger->warning(
"Current revision content format unsupported. Overriding {prev} to {new}", [
1372 'prev' => $this->contentFormat,
1373 'new' =>
$rev->getContentFormat(),
1374 'title' => $this->
getTitle()->getPrefixedDBkey(),
1375 'method' => __METHOD__
1377 $this->contentFormat =
$rev->getContentFormat();
1391 $this->mPreloadContent = $content;
1406 if ( !empty( $this->mPreloadContent ) ) {
1412 if ( $preload ===
'' ) {
1413 return $handler->makeEmptyContent();
1416 $user = $this->context->getUser();
1418 # Check for existence to avoid getting MediaWiki:Noarticletext
1421 return $handler->makeEmptyContent();
1430 return $handler->makeEmptyContent();
1440 return $handler->makeEmptyContent();
1443 if ( $content->getModel() !==
$handler->getModelID() ) {
1444 $converted = $content->convert(
$handler->getModelID() );
1446 if ( !$converted ) {
1448 wfDebug(
"Attempt to preload incompatible content: " .
1449 "can't convert " . $content->getModel() .
1452 return $handler->makeEmptyContent();
1455 $content = $converted;
1458 return $content->preloadTransform(
$title, $parserOptions,
$params );
1469 $token =
$request->getVal(
'wpEditToken' );
1470 $user = $this->context->getUser();
1471 $this->mTokenOk =
$user->matchEditToken( $token );
1472 $this->mTokenOkExceptSuffix =
$user->matchEditTokenNoSuffix( $token );
1491 $revisionId = $this->
page->getLatest();
1492 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1495 if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1497 } elseif ( $this->oldid ) {
1501 $response = $this->context->getRequest()->response();
1502 $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1512 # Allow bots to exempt some edits from bot flagging
1513 $bot = $this->context->getUser()->isAllowed(
'bot' ) &&
$this->bot;
1516 Hooks::run(
'EditPage::attemptSave:after', [ $this,
$status, $resultDetails ] );
1525 if ( $this->context->getRequest()->getText(
'mode' ) !==
'conflict' ) {
1529 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1530 $stats->increment(
'edit.failures.conflict.resolved' );
1547 if (
$status->value == self::AS_SUCCESS_UPDATE
1548 ||
$status->value == self::AS_SUCCESS_NEW_ARTICLE
1552 $this->didSave =
true;
1553 if ( !$resultDetails[
'nullEdit'] ) {
1558 $out = $this->context->getOutput();
1562 $request = $this->context->getRequest();
1563 $extraQueryRedirect =
$request->getVal(
'wpExtraQueryRedirect' );
1584 $out->addWikiText(
'<div class="error">' .
"\n" .
$status->getWikiText() .
'</div>' );
1588 $query = $resultDetails[
'redirect'] ?
'redirect=no' :
'';
1589 if ( $extraQueryRedirect ) {
1591 $query = $extraQueryRedirect;
1596 $anchor = isset( $resultDetails[
'sectionanchor'] ) ? $resultDetails[
'sectionanchor'] :
'';
1597 $out->redirect( $this->mTitle->getFullURL(
$query ) . $anchor );
1602 $sectionanchor = $resultDetails[
'sectionanchor'];
1606 'ArticleUpdateBeforeRedirect',
1607 [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1610 if ( $resultDetails[
'redirect'] ) {
1611 if ( $extraQuery ==
'' ) {
1612 $extraQuery =
'redirect=no';
1614 $extraQuery =
'redirect=no&' . $extraQuery;
1617 if ( $extraQueryRedirect ) {
1618 if ( $extraQuery ===
'' ) {
1619 $extraQuery = $extraQueryRedirect;
1621 $extraQuery = $extraQuery .
'&' . $extraQueryRedirect;
1625 $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1650 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
1661 $this->hookError =
'<div class="error">' .
"\n" .
$status->getWikiText() .
1678 if ( $this->hookError !=
'' ) {
1679 # ...or the hook could be expecting us to produce an error
1680 $status->fatal(
'hookaborted' );
1687 [ $this->context, $content,
$status, $this->summary,
1688 $user, $this->minoredit ] )
1690 # Error messages etc. could be handled within the hook...
1692 $status->fatal(
'hookaborted' );
1698 $this->hookError =
$status->getWikiText();
1705 } elseif ( !
$status->isOK() ) {
1706 # ...or the hook could be expecting us to produce an error
1708 $this->hookError =
$status->getWikiText();
1709 $status->fatal(
'hookaborted' );
1726 if ( $this->sectiontitle !==
'' ) {
1731 if ( $this->summary ===
'' ) {
1732 $cleanSectionTitle =
$wgParser->stripSectionName( $this->sectiontitle );
1733 return $this->context->msg(
'newsectionsummary' )
1734 ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1736 } elseif ( $this->summary !==
'' ) {
1738 # This is a new section, so create a link to the new section
1739 # in the revision summary.
1740 $cleanSummary =
$wgParser->stripSectionName( $this->summary );
1741 return $this->context->msg(
'newsectionsummary' )
1742 ->rawParams( $cleanSummary )->inContentLanguage()->text();
1773 $user = $this->context->getUser();
1775 if ( !
Hooks::run(
'EditPage::attemptSave', [ $this ] ) ) {
1776 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving\n" );
1777 $status->fatal(
'hookaborted' );
1782 if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1783 $status->fatal(
'unicode-support-fail' );
1788 $request = $this->context->getRequest();
1789 $spam =
$request->getText(
'wpAntispam' );
1790 if ( $spam !==
'' ) {
1795 $this->mTitle->getPrefixedText() .
1796 '" submitted bogus field "' .
1800 $status->fatal(
'spamprotectionmatch',
false );
1806 # Construct Content object
1810 'content-failed-to-parse',
1811 $this->contentModel,
1812 $this->contentFormat,
1819 # Check image redirect
1820 if ( $this->mTitle->getNamespace() ==
NS_FILE &&
1821 $textbox_content->isRedirect() &&
1822 !
$user->isAllowed(
'upload' )
1832 if ( $match ===
false && $this->section ==
'new' ) {
1833 # $wgSpamRegex is enforced on this new heading/summary because, unlike
1834 # regular summaries, it is added to the actual wikitext.
1835 if ( $this->sectiontitle !==
'' ) {
1836 # This branch is taken when the API is used with the 'sectiontitle' parameter.
1839 # This branch is taken when the "Add Topic" user interface is used, or the API
1840 # is used with the 'summary' parameter.
1844 if ( $match ===
false ) {
1847 if ( $match !==
false ) {
1850 $pdbk = $this->mTitle->getPrefixedDBkey();
1851 $match = str_replace(
"\n",
'', $match );
1852 wfDebugLog(
'SpamRegex',
"$ip spam regex hit [[$pdbk]]: \"$match\"" );
1853 $status->fatal(
'spamprotectionmatch', $match );
1859 [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1861 # Error messages etc. could be handled within the hook...
1862 $status->fatal(
'hookaborted' );
1865 } elseif ( $this->hookError !=
'' ) {
1866 # ...or the hook could be expecting us to produce an error
1867 $status->fatal(
'hookaborted' );
1872 if (
$user->isBlockedFrom( $this->mTitle,
false ) ) {
1875 $user->spreadAnyEditBlock();
1877 # Check block state against master, thus 'false'.
1878 $status->setResult(
false, self::AS_BLOCKED_PAGE_FOR_USER );
1882 $this->contentLength = strlen( $this->textbox1 );
1883 $config = $this->context->getConfig();
1884 $maxArticleSize = $config->get(
'MaxArticleSize' );
1885 if ( $this->contentLength > $maxArticleSize * 1024 ) {
1887 $this->tooBig =
true;
1888 $status->setResult(
false, self::AS_CONTENT_TOO_BIG );
1892 if ( !
$user->isAllowed(
'edit' ) ) {
1893 if (
$user->isAnon() ) {
1894 $status->setResult(
false, self::AS_READ_ONLY_PAGE_ANON );
1897 $status->fatal(
'readonlytext' );
1903 $changingContentModel =
false;
1904 if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1905 if ( !$config->get(
'ContentHandlerUseDB' ) ) {
1906 $status->fatal(
'editpage-cannot-use-custom-model' );
1909 } elseif ( !
$user->isAllowed(
'editcontentmodel' ) ) {
1910 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1916 if ( !$titleWithNewContentModel->userCan(
'editcontentmodel',
$user )
1917 || !$titleWithNewContentModel->userCan(
'edit',
$user )
1919 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1923 $changingContentModel =
true;
1924 $oldContentModel = $this->mTitle->getContentModel();
1927 if ( $this->changeTags ) {
1929 $this->changeTags,
$user );
1930 if ( !$changeTagsStatus->isOK() ) {
1932 return $changeTagsStatus;
1937 $status->fatal(
'readonlytext' );
1941 if (
$user->pingLimiter() ||
$user->pingLimiter(
'linkpurge', 0 )
1942 || ( $changingContentModel &&
$user->pingLimiter(
'editcontentmodel' ) )
1944 $status->fatal(
'actionthrottledtext' );
1949 # If the article has been deleted while editing, don't save it without
1952 $status->setResult(
false, self::AS_ARTICLE_WAS_DELETED );
1956 # Load the page data from the master. If anything changes in the meantime,
1957 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1958 $this->
page->loadPageData(
'fromdbmaster' );
1959 $new = !$this->
page->exists();
1963 if ( !$this->mTitle->userCan(
'create',
$user ) ) {
1964 $status->fatal(
'nocreatetext' );
1966 wfDebug( __METHOD__ .
": no create permission\n" );
1973 $defaultMessageText = $this->mTitle->getDefaultMessageText();
1974 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI && $defaultMessageText !==
false ) {
1975 $defaultText = $defaultMessageText;
1980 if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1981 $this->blankArticle =
true;
1982 $status->fatal(
'blankarticle' );
1983 $status->setResult(
false, self::AS_BLANK_ARTICLE );
1991 $content = $textbox_content;
1993 $result[
'sectionanchor'] =
'';
1994 if ( $this->section ==
'new' ) {
1995 if ( $this->sectiontitle !==
'' ) {
1997 $content = $content->addSectionHeader( $this->sectiontitle );
1998 } elseif ( $this->summary !==
'' ) {
2000 $content = $content->addSectionHeader( $this->summary );
2009 # Article exists. Check for edit conflict.
2011 $this->
page->clear(); # Force reload
of dates,
etc.
2012 $timestamp = $this->
page->getTimestamp();
2013 $latest = $this->
page->getLatest();
2015 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2018 if ( $timestamp != $this->edittime
2019 || ( $this->editRevId !==
null && $this->editRevId != $latest )
2021 $this->isConflict =
true;
2022 if ( $this->section ==
'new' ) {
2023 if ( $this->
page->getUserText() ==
$user->getName() &&
2030 .
": duplicate new section submission; trigger edit conflict!\n" );
2033 $this->isConflict =
false;
2034 wfDebug( __METHOD__ .
": conflict suppressed; new section\n" );
2036 } elseif ( $this->section ==
''
2038 DB_MASTER, $this->mTitle->getArticleID(),
2042 # Suppress edit conflict with self, except for section edits where merging is required.
2043 wfDebug( __METHOD__ .
": Suppressing edit conflict, same user.\n" );
2044 $this->isConflict =
false;
2049 if ( $this->sectiontitle !==
'' ) {
2057 if ( $this->isConflict ) {
2059 .
": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2060 .
" (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2063 if ( $this->editRevId !==
null ) {
2064 $content = $this->
page->replaceSectionAtRev(
2071 $content = $this->
page->replaceSectionContent(
2079 wfDebug( __METHOD__ .
": getting section '{$this->section}'\n" );
2080 $content = $this->
page->replaceSectionContent(
2087 if ( is_null( $content ) ) {
2088 wfDebug( __METHOD__ .
": activating conflict; section replace failed.\n" );
2089 $this->isConflict =
true;
2090 $content = $textbox_content;
2091 } elseif ( $this->isConflict ) {
2095 $this->isConflict =
false;
2096 wfDebug( __METHOD__ .
": Suppressing edit conflict, successful merge.\n" );
2098 $this->section =
'';
2100 wfDebug( __METHOD__ .
": Keeping edit conflict, failed merge.\n" );
2104 if ( $this->isConflict ) {
2105 $status->setResult(
false, self::AS_CONFLICT_DETECTED );
2113 if ( $this->section ==
'new' ) {
2115 if ( !$this->allowBlankSummary && trim( $this->summary ) ==
'' ) {
2116 $this->missingSummary =
true;
2117 $status->fatal(
'missingsummary' );
2123 if ( $this->textbox1 ==
'' ) {
2124 $this->missingComment =
true;
2125 $status->fatal(
'missingcommenttext' );
2129 } elseif ( !$this->allowBlankSummary
2130 && !$content->equals( $this->getOriginalContent(
$user ) )
2131 && !$content->isRedirect()
2134 $this->missingSummary =
true;
2135 $status->fatal(
'missingsummary' );
2141 $sectionanchor =
'';
2142 if ( $this->section ==
'new' ) {
2144 } elseif ( $this->section !=
'' ) {
2145 # Try to get a section anchor from the section source, redirect
2146 # to edited section if header found.
2147 # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2148 # for duplicate heading checking and maybe parsing.
2149 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
2150 # We can't deal with anchors, includes, html etc in the header for now,
2151 # headline would need to be parsed to improve this.
2152 if ( $hasmatch && strlen(
$matches[2] ) > 0 ) {
2156 $result[
'sectionanchor'] = $sectionanchor;
2162 $this->textbox1 = $this->
toEditText( $content );
2163 $this->section =
'';
2168 if ( !$this->allowSelfRedirect
2169 && $content->isRedirect()
2170 && $content->getRedirectTarget()->equals( $this->
getTitle() )
2174 if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2175 $this->selfRedirect =
true;
2176 $status->fatal(
'selfredirect' );
2183 $this->contentLength = strlen( $this->
toEditText( $content ) );
2184 if ( $this->contentLength > $maxArticleSize * 1024 ) {
2185 $this->tooBig =
true;
2186 $status->setResult(
false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2192 ( ( $this->minoredit && !$this->isNew ) ?
EDIT_MINOR : 0 ) |
2195 $doEditStatus = $this->
page->doEditContent(
2201 $content->getDefaultFormat(),
2206 if ( !$doEditStatus->isOK() ) {
2210 $errors = $doEditStatus->getErrorsArray();
2211 if ( in_array( $errors[0][0],
2212 [
'edit-gone-missing',
'edit-conflict',
'edit-already-exists' ] )
2214 $this->isConflict =
true;
2218 return $doEditStatus;
2221 $result[
'nullEdit'] = $doEditStatus->hasMessage(
'edit-no-change' );
2224 $user->pingLimiter(
'linkpurge' );
2226 $result[
'redirect'] = $content->isRedirect();
2231 if ( $changingContentModel ) {
2234 $new ?
false : $oldContentModel,
2235 $this->contentModel,
2250 $new = $oldModel ===
false;
2251 $log =
new ManualLogEntry(
'contentmodel', $new ?
'new' :
'change' );
2252 $log->setPerformer(
$user );
2253 $log->setTarget( $this->mTitle );
2254 $log->setComment( $reason );
2255 $log->setParameters( [
2256 '4::oldmodel' => $oldModel,
2257 '5::newmodel' => $newModel
2259 $logid = $log->insert();
2260 $log->publish( $logid );
2267 $user = $this->context->getUser();
2268 if ( !
$user->isLoggedIn() ) {
2299 $baseContent = $baseRevision ? $baseRevision->getContent() :
null;
2301 if ( is_null( $baseContent ) ) {
2307 $currentContent = $currentRevision ? $currentRevision->getContent() :
null;
2309 if ( is_null( $currentContent ) ) {
2315 $result =
$handler->merge3( $baseContent, $editContent, $currentContent );
2320 $this->parentRevId = $currentRevision->getId();
2333 if ( !$this->mBaseRevision ) {
2335 $this->mBaseRevision = $this->editRevId
2377 if ( preg_match( $regex, $text,
$matches ) ) {
2385 $out = $this->context->getOutput();
2387 $out->addModules(
'mediawiki.action.edit' );
2388 $out->addModuleStyles(
'mediawiki.action.edit.styles' );
2390 $user = $this->context->getUser();
2391 if (
$user->getOption(
'showtoolbar' ) ) {
2396 $out->addModules(
'mediawiki.toolbar' );
2399 if (
$user->getOption(
'uselivepreview' ) ) {
2400 $out->addModules(
'mediawiki.action.edit.preview' );
2403 if (
$user->getOption(
'useeditwarning' ) ) {
2404 $out->addModules(
'mediawiki.action.edit.editWarning' );
2407 # Enabled article-related sidebar, toplinks, etc.
2408 $out->setArticleRelated(
true );
2411 if ( $this->isConflict ) {
2412 $msg =
'editconflict';
2413 } elseif ( $contextTitle->exists() && $this->section !=
'' ) {
2414 $msg = $this->section ==
'new' ?
'editingcomment' :
'editingsection';
2416 $msg = $contextTitle->exists()
2418 && $contextTitle->getDefaultMessageText() !==
false
2424 # Use the title defined by DISPLAYTITLE magic word when present
2425 # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2426 # setPageTitle() treats the input as wikitext, which should be safe in either case.
2427 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() :
false;
2428 if ( $displayTitle ===
false ) {
2429 $displayTitle = $contextTitle->getPrefixedText();
2431 $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2432 # Transmit the name of the message to JavaScript for live preview
2433 # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2434 $out->addJsConfigVars( [
2435 'wgEditMessage' => $msg,
2436 'wgAjaxEditStash' => $this->context->getConfig()->get(
'AjaxEditStash' ),
2444 if ( $this->suppressIntro ) {
2448 $out = $this->context->getOutput();
2449 $namespace = $this->mTitle->getNamespace();
2452 # Show a warning if editing an interface message
2453 $out->wrapWikiMsg(
"<div class='mw-editinginterface'>\n$1\n</div>",
'editinginterface' );
2454 # If this is a default message (but not css or js),
2455 # show a hint that it is translatable on translatewiki.net
2459 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2460 if ( $defaultMessageText !==
false ) {
2461 $out->wrapWikiMsg(
"<div class='mw-translateinterface'>\n$1\n</div>",
2462 'translateinterface' );
2465 } elseif ( $namespace ==
NS_FILE ) {
2466 # Show a hint to shared repo
2468 if ( $file && !$file->isLocal() ) {
2469 $descUrl = $file->getDescriptionUrl();
2470 # there must be a description url to show a hint to shared repo
2472 if ( !$this->mTitle->exists() ) {
2473 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2474 'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2477 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2478 'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2485 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2486 # Show log extract when the user is currently blocked
2488 $username = explode(
'/', $this->mTitle->getText(), 2 )[0];
2493 $out->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2496 # Show log extract if the user is currently blocked
2504 'showIfEmpty' =>
false,
2506 'blocked-notice-logextract',
2507 $user->getName() # Support GENDER
in notice
2513 # Try to add a custom edit intro, or use the standard one if this is not possible.
2516 $this->context->msg(
'helppage' )->inContentLanguage()->text()
2518 if ( $this->context->getUser()->isLoggedIn() ) {
2521 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2530 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2532 'newarticletextanon',
2538 # Give a notice if the user is editing a deleted/moved page...
2539 if ( !$this->mTitle->exists() ) {
2546 'conds' => [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ],
2547 'showIfEmpty' =>
false,
2548 'msgKey' => [
'recreate-moveddeleted-warn' ]
2560 if ( $this->editintro ) {
2564 $this->context->getOutput()->addWikiTextTitleTidy(
2565 '<div class="mw-editintro">{{:' .
$title->getFullText() .
'}}</div>',
2593 if ( $content ===
null || $content ===
false || is_string( $content ) ) {
2598 throw new MWException(
'This content model is not supported: ' . $content->getModel() );
2601 return $content->serialize( $this->contentFormat );
2621 if ( $text ===
false || $text ===
null ) {
2626 $this->contentModel, $this->contentFormat );
2629 throw new MWException(
'This content model is not supported: ' . $content->getModel() );
2644 # need to parse the preview early so that we know which templates are used,
2645 # otherwise users with "show preview after edit box" will get a blank list
2646 # we parse this near the beginning so that setHeaders can do the title
2647 # setting work instead of leaving it in getPreviewText
2648 $previewOutput =
'';
2649 if ( $this->formtype ==
'preview' ) {
2653 $out = $this->context->getOutput();
2657 Hooks::run(
'EditPage::showEditForm:initial', [ &$editPage, &
$out ] );
2664 if ( !$this->isConflict &&
2665 $this->section !=
'' &&
2670 $out->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
2676 $out->addHTML( $this->editFormPageTop );
2678 $user = $this->context->getUser();
2679 if (
$user->getOption(
'previewontop' ) ) {
2683 $out->addHTML( $this->editFormTextTop );
2685 $showToolbar =
true;
2687 if ( $this->formtype ==
'save' ) {
2690 $showToolbar =
false;
2692 $out->wrapWikiMsg(
"<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2693 'deletedwhileediting' );
2702 'class' =>
'mw-editform',
2703 'id' => self::EDITFORM_ID,
2704 'name' => self::EDITFORM_ID,
2707 'enctype' =>
'multipart/form-data'
2711 if ( is_callable( $formCallback ) ) {
2712 wfWarn(
'The $formCallback parameter to ' . __METHOD__ .
'is deprecated' );
2713 call_user_func_array( $formCallback, [ &
$out ] );
2721 Xml::openElement(
'div', [
'id' =>
'antispam-container',
'style' =>
'display: none;' ] )
2724 [
'for' =>
'wpAntispam' ],
2725 $this->context->msg(
'simpleantispam-label' )->parse()
2731 'name' =>
'wpAntispam',
2732 'id' =>
'wpAntispam',
2741 Hooks::run(
'EditPage::showEditForm:fields', [ &$editPage, &
$out ] );
2747 $username = $this->lastDelete->user_name;
2752 $key = $comment ===
''
2753 ?
'confirmrecreate-noreason'
2754 :
'confirmrecreate';
2756 '<div class="mw-confirm-recreate">' .
2757 $this->context->msg( $key,
$username,
"<nowiki>$comment</nowiki>" )->parse() .
2758 Xml::checkLabel( $this->context->msg(
'recreate' )->text(),
'wpRecreate',
'wpRecreate',
false,
2765 # When the summary is hidden, also hide them on preview/show changes
2766 if ( $this->nosummary ) {
2770 # If a blank edit summary was previously provided, and the appropriate
2771 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2772 # user being bounced back more than once in the event that a summary
2775 # For a bit more sophisticated detection of blank summaries, hash the
2776 # automatic one and pass that in the hidden field wpAutoSummary.
2777 if ( $this->missingSummary || ( $this->section ==
'new' && $this->nosummary ) ) {
2781 if ( $this->undidRev ) {
2785 if ( $this->selfRedirect ) {
2789 if ( $this->hasPresetSummary ) {
2793 $this->autoSumm = md5(
'' );
2796 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2807 if ( $this->section ==
'new' ) {
2812 $out->addHTML( $this->editFormTextBeforeContent );
2814 if ( !$this->mTitle->isCssJsSubpage() && $showToolbar &&
$user->getOption(
'showtoolbar' ) ) {
2815 $out->addHTML( self::getEditToolbar( $this->mTitle ) );
2818 if ( $this->blankArticle ) {
2822 if ( $this->isConflict ) {
2830 $this->textbox1 = $this->
toEditText( $content );
2837 $out->addHTML( $this->editFormTextAfterContent );
2847 $out->addHTML( $this->editFormTextAfterTools .
"\n" );
2855 self::getPreviewLimitReport( $this->mParserOutput ) ) );
2857 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
2859 if ( $this->isConflict ) {
2864 $msg = $this->context->msg(
2865 'content-failed-to-parse',
2866 $this->contentModel,
2867 $this->contentFormat,
2870 $out->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
2875 if ( $this->isConflict ) {
2877 } elseif ( $this->preview ) {
2879 } elseif ( $this->
diff ) {
2884 $out->addHTML(
Html::hidden(
'mode', $mode, [
'id' =>
'mw-edit-mode' ] ) );
2889 $out->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
2891 if ( !
$user->getOption(
'previewontop' ) ) {
2905 $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2910 if ( $this->preview ) {
2912 } elseif ( $this->section !=
'' ) {
2917 $templateListFormatter->format( $templates,
$type )
2928 preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches );
2938 $out = $this->context->getOutput();
2939 $user = $this->context->getUser();
2940 if ( $this->isConflict ) {
2942 $this->editRevId = $this->
page->getLatest();
2944 if ( $this->section !=
'' && $this->section !=
'new' ) {
2945 if ( !$this->summary && !$this->preview && !$this->
diff ) {
2947 if ( $sectionTitle !==
false ) {
2948 $this->summary =
"/* $sectionTitle */ ";
2955 if ( $this->missingComment ) {
2956 $out->wrapWikiMsg(
"<div id='mw-missingcommenttext'>\n$1\n</div>",
'missingcommenttext' );
2959 if ( $this->missingSummary && $this->section !=
'new' ) {
2961 "<div id='mw-missingsummary'>\n$1\n</div>",
2962 [
'missingsummary', $buttonLabel ]
2966 if ( $this->missingSummary && $this->section ==
'new' ) {
2968 "<div id='mw-missingcommentheader'>\n$1\n</div>",
2969 [
'missingcommentheader', $buttonLabel ]
2973 if ( $this->blankArticle ) {
2975 "<div id='mw-blankarticle'>\n$1\n</div>",
2976 [
'blankarticle', $buttonLabel ]
2980 if ( $this->selfRedirect ) {
2982 "<div id='mw-selfredirect'>\n$1\n</div>",
2983 [
'selfredirect', $buttonLabel ]
2987 if ( $this->hookError !==
'' ) {
2988 $out->addWikiText( $this->hookError );
2991 if ( $this->section !=
'new' ) {
2992 $revision = $this->mArticle->getRevisionFetched();
2998 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2999 'rev-deleted-text-permission'
3003 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3004 'rev-deleted-text-view'
3008 if ( !$revision->isCurrent() ) {
3009 $this->mArticle->setOldSubtitle( $revision->getId() );
3010 $out->addWikiMsg(
'editingold' );
3011 $this->isOldRev =
true;
3013 } elseif ( $this->mTitle->exists() ) {
3016 $out->wrapWikiMsg(
"<div class='errorbox'>\n$1\n</div>\n",
3017 [
'missing-revision', $this->oldid ] );
3024 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3027 } elseif (
$user->isAnon() ) {
3028 if ( $this->formtype !=
'preview' ) {
3030 "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3031 [
'anoneditwarning',
3034 'returnto' => $this->
getTitle()->getPrefixedDBkey()
3038 'returnto' => $this->
getTitle()->getPrefixedDBkey()
3043 $out->wrapWikiMsg(
"<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3044 'anonpreviewwarning'
3048 if ( $this->mTitle->isCssJsSubpage() ) {
3049 # Check the skin exists
3052 "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
3053 [
'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
3056 if ( $this->
getTitle()->isSubpageOf(
$user->getUserPage() ) ) {
3058 $out->wrapWikiMsg(
'<div class="mw-usercssjspublic">$1</div>',
3061 if ( $this->formtype !==
'preview' ) {
3062 $config = $this->context->getConfig();
3065 "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3066 [
'usercssyoucanpreview' ]
3070 if ( $this->mTitle->isJsSubpage() && $config->get(
'AllowUserJs' ) ) {
3072 "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3073 [
'userjsyoucanpreview' ]
3085 # Add header copyright warning
3098 return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3099 'id' =>
'wpSummary',
3100 'name' =>
'wpSummary',
3101 'maxlength' =>
'200',
3104 'spellcheck' =>
'true',
3123 $inputAttrs =
null, $spanLabelAttrs =
null
3129 $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
3130 'class' => $this->missingSummary ?
'mw-summarymissed' :
'mw-summary',
3131 'id' =>
"wpSummaryLabel"
3138 $inputAttrs[
'id'] ? [
'for' => $inputAttrs[
'id'] ] :
null,
3141 $label =
Xml::tags(
'span', $spanLabelAttrs, $label );
3146 return [ $label,
$input ];
3174 $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3183 $inputAttrs[
'inputId'] = $inputAttrs[
'id'];
3184 $inputAttrs[
'id'] =
'wpSummaryWidget';
3186 return new OOUI\FieldLayout(
3187 new OOUI\TextInputWidget( [
3189 'infusable' =>
true,
3192 'label' =>
new OOUI\HtmlSnippet( $labelText ),
3194 'id' =>
'wpSummaryLabel',
3195 'classes' => [ $this->missingSummary ?
'mw-summarymissed' :
'mw-summary' ],
3207 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3208 $summaryClass = $this->missingSummary ?
'mw-summarymissed' :
'mw-summary';
3209 if ( $isSubjectPreview ) {
3210 if ( $this->nosummary ) {
3214 if ( !$this->mShowSummaryField ) {
3219 $labelText = $this->context->msg( $isSubjectPreview ?
'subject' :
'summary' )->parse();
3223 [
'class' => $summaryClass ]
3237 if ( !
$summary || ( !$this->preview && !$this->
diff ) ) {
3243 if ( $isSubjectPreview ) {
3244 $summary = $this->context->msg(
'newsectionsummary' )
3246 ->inContentLanguage()->text();
3249 $message = $isSubjectPreview ?
'subject-preview' :
'summary-preview';
3251 $summary = $this->context->msg( $message )->parse()
3257 $out = $this->context->getOutput();
3262 $out->addHTML(
Html::hidden(
'wpScrolltop', $this->scrolltop, [
'id' =>
'wpScrolltop' ] ) );
3278 $this->context->getOutput()->addHTML(
3280 Html::hidden(
"wpEditToken", $this->context->getUser()->getEditToken() ) .
3307 $attribs = [
'style' =>
'display:none;' ];
3310 if ( $this->mTitle->isProtected(
'edit' ) &&
3313 # Is the title semi-protected?
3314 if ( $this->mTitle->isSemiProtected() ) {
3315 $classes[] =
'mw-textarea-sprotected';
3317 # Then it must be protected based on static groups (regular)
3318 $classes[] =
'mw-textarea-protected';
3320 # Is the title cascade-protected?
3321 if ( $this->mTitle->isCascadeProtected() ) {
3322 $classes[] =
'mw-textarea-cprotected';
3325 # Is an old revision being edited?
3326 if ( $this->isOldRev ) {
3327 $classes[] =
'mw-textarea-oldrev';
3336 if (
count( $classes ) ) {
3337 if ( isset(
$attribs[
'class'] ) ) {
3340 $attribs[
'class'] = implode(
' ', $classes );
3345 $textoverride !==
null ? $textoverride : $this->textbox1,
3352 $this->
showTextbox( $this->textbox2,
'wpTextbox2', [
'tabindex' => 6,
'readonly' ] );
3366 $classes[] =
'ontop';
3369 $attribs = [
'id' =>
'wikiPreview',
'class' => implode(
' ', $classes ) ];
3371 if ( $this->formtype !=
'preview' ) {
3372 $attribs[
'style'] =
'display: none;';
3375 $out = $this->context->getOutput();
3378 if ( $this->formtype ==
'preview' ) {
3382 $pageViewLang = $this->mTitle->getPageViewLanguage();
3383 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3384 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3388 $out->addHTML(
'</div>' );
3390 if ( $this->formtype ==
'diff' ) {
3394 $msg = $this->context->msg(
3395 'content-failed-to-parse',
3396 $this->contentModel,
3397 $this->contentFormat,
3400 $out->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
3413 $this->mArticle->openShowCategory();
3415 # This hook seems slightly odd here, but makes things more
3416 # consistent for extensions.
3417 $out = $this->context->getOutput();
3419 $out->addHTML( $text );
3421 $this->mArticle->closeShowCategory();
3435 $oldtitlemsg =
'currentrev';
3436 # if message does not exist, show diff against the preloaded default
3437 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3438 $oldtext = $this->mTitle->getDefaultMessageText();
3439 if ( $oldtext !==
false ) {
3440 $oldtitlemsg =
'defaultmessagetext';
3450 if ( $this->editRevId !==
null ) {
3451 $newContent = $this->
page->replaceSectionAtRev(
3452 $this->section, $textboxContent, $this->summary, $this->editRevId
3455 $newContent = $this->
page->replaceSectionContent(
3456 $this->section, $textboxContent, $this->summary, $this->edittime
3460 if ( $newContent ) {
3461 Hooks::run(
'EditPageGetDiffContent', [ $this, &$newContent ] );
3463 $user = $this->context->getUser();
3465 $newContent = $newContent->preSaveTransform( $this->mTitle,
$user, $popts );
3468 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3469 $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3470 $newtitle = $this->context->msg(
'yourtext' )->parse();
3472 if ( !$oldContent ) {
3473 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3476 if ( !$newContent ) {
3477 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3480 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3481 $de->setContent( $oldContent, $newContent );
3483 $difftext = $de->getDiff( $oldtitle, $newtitle );
3484 $de->showDiffStyle();
3489 $this->context->getOutput()->addHTML(
'<div id="wikiDiff">' . $difftext .
'</div>' );
3496 $msg =
'editpage-head-copy-warn';
3497 if ( !$this->context->msg( $msg )->isDisabled() ) {
3498 $this->context->getOutput()->wrapWikiMsg(
"<div class='editpage-head-copywarn'>\n$1\n</div>",
3499 'editpage-head-copy-warn' );
3512 $msg =
'editpage-tos-summary';
3513 Hooks::run(
'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3514 if ( !$this->context->msg( $msg )->isDisabled() ) {
3515 $out = $this->context->getOutput();
3516 $out->addHTML(
'<div class="mw-tos-summary">' );
3517 $out->addWikiMsg( $msg );
3518 $out->addHTML(
'</div>' );
3527 $this->context->getOutput()->addHTML(
'<div class="mw-editTools">' .
3528 $this->context->msg(
'edittools' )->inContentLanguage()->parse() .
3553 $copywarnMsg = [
'copyrightwarning',
3554 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]',
3557 $copywarnMsg = [
'copyrightwarning2',
3558 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]' ];
3563 $msg = call_user_func_array(
'wfMessage', $copywarnMsg )->title(
$title );
3565 $msg->inLanguage( $langcode );
3567 return "<div id=\"editpage-copywarn\">\n" .
3568 $msg->$format() .
"\n</div>";
3583 $limitReport =
Html::rawElement(
'div', [
'class' =>
'mw-limitReportExplanation' ],
3584 wfMessage(
'limitreport-title' )->parseAsBlock()
3588 $limitReport .=
Html::openElement(
'div', [
'class' =>
'preview-limit-report-wrapper' ] );
3591 'class' =>
'preview-limit-report wikitable'
3597 [ $key, &
$value, &$limitReport,
true,
true ]
3600 $valueMsg =
wfMessage( [
"$key-value-html",
"$key-value" ] );
3601 if ( !$valueMsg->exists() ) {
3604 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3617 return $limitReport;
3621 $out = $this->context->getOutput();
3622 $out->addHTML(
"<div class='editOptions'>\n" );
3624 if ( $this->section !=
'new' ) {
3631 [
'minor' => $this->minoredit,
'watch' => $this->watchthis ]
3633 $checkboxesHTML =
new OOUI\HorizontalLayout( [
'items' => $checkboxes ] );
3635 $out->addHTML(
"<div class='editCheckboxes'>" . $checkboxesHTML .
"</div>\n" );
3639 $out->addHTML( $this->editFormTextAfterWarn );
3641 $out->addHTML(
"<div class='editButtons'>\n" );
3645 if ( $cancel !==
'' ) {
3647 [
'class' =>
'mw-editButtons-pipe-separator' ],
3648 $this->context->msg(
'pipe-separator' )->text() );
3651 $message = $this->context->msg(
'edithelppage' )->inContentLanguage()->text();
3655 $this->context->msg(
'edithelp' )->text(),
3656 [
'target' =>
'helpwindow',
'href' => $edithelpurl ],
3659 $this->context->msg(
'word-separator' )->escaped() .
3660 $this->context->msg(
'newwindow' )->parse();
3662 $out->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3663 $out->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3664 $out->addHTML(
"</div><!-- editButtons -->\n" );
3668 $out->addHTML(
"</div><!-- editOptions -->\n" );
3676 $out = $this->context->getOutput();
3679 if (
Hooks::run(
'EditPageBeforeConflictDiff', [ &$editPage, &
$out ] ) ) {
3682 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
3688 $de =
$handler->createDifferenceEngine( $this->context );
3689 $de->setContent( $content2, $content1 );
3691 $this->context->msg(
'yourtext' )->parse(),
3692 $this->context->msg(
'storedversion' )->text()
3695 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
3701 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3702 $stats->increment(
'edit.failures.conflict' );
3705 $this->mTitle->getNamespace() >=
NS_MAIN &&
3708 $stats->increment(
'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
3717 if ( !$this->isConflict && $this->oldid > 0 ) {
3720 $cancelParams[
'redirect'] =
'no';
3723 return new OOUI\ButtonWidget( [
3724 'id' =>
'mw-editform-cancel',
3726 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
'cancel' )->parse() ),
3728 'infusable' =>
true,
3729 'flags' =>
'destructive',
3743 return $title->getLocalURL( [
'action' => $this->
action ] );
3754 if ( $this->deletedSinceEdit !==
null ) {
3758 $this->deletedSinceEdit =
false;
3760 if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3762 if ( $this->lastDelete ) {
3763 $deleteTime =
wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3764 if ( $deleteTime > $this->starttime ) {
3765 $this->deletedSinceEdit =
true;
3779 $data =
$dbr->selectRow(
3780 [
'logging',
'user' ] + $commentQuery[
'tables'],
3791 ] + $commentQuery[
'fields'], [
3792 'log_namespace' => $this->mTitle->getNamespace(),
3793 'log_title' => $this->mTitle->getDBkey(),
3794 'log_type' =>
'delete',
3795 'log_action' =>
'delete',
3799 [
'LIMIT' => 1,
'ORDER BY' =>
'log_timestamp DESC' ],
3801 'user' => [
'JOIN',
'user_id=log_user' ],
3802 ] + $commentQuery[
'joins']
3805 if ( is_object( $data ) ) {
3807 $data->user_name = $this->context->msg(
'rev-deleted-user' )->escaped();
3811 $data->log_comment_text = $this->context->msg(
'rev-deleted-comment' )->escaped();
3812 $data->log_comment_data =
null;
3825 $out = $this->context->getOutput();
3826 $config = $this->context->getConfig();
3832 if ( $this->textbox1 !==
'' ) {
3836 $parsedNote =
$out->parse(
"<div class='previewnote'>" .
3837 $this->context->msg(
'session_fail_preview_html' )->text() .
"</div>",
3851 'AlternateEditPreview',
3852 [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3854 return $previewHTML;
3857 # provide a anchor link to the editform
3858 $continueEditing =
'<span class="mw-continue-editing">' .
3859 '[[#' . self::EDITFORM_ID .
'|' .
3860 $this->context->getLanguage()->getArrow() .
' ' .
3861 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
3862 if ( $this->mTriedSave && !$this->mTokenOk ) {
3863 if ( $this->mTokenOkExceptSuffix ) {
3864 $note = $this->context->msg(
'token_suffix_mismatch' )->plain();
3867 $note = $this->context->msg(
'session_fail_preview' )->plain();
3870 } elseif ( $this->incompleteForm ) {
3871 $note = $this->context->msg(
'edit_form_incomplete' )->plain();
3872 if ( $this->mTriedSave ) {
3876 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
3879 # don't parse non-wikitext pages, show message about preview
3880 if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3881 if ( $this->mTitle->isCssJsSubpage() ) {
3883 } elseif ( $this->mTitle->isCssOrJsPage() ) {
3891 if ( $level ===
'user' && !$config->get(
'AllowUserCss' ) ) {
3896 if ( $level ===
'user' && !$config->get(
'AllowUserJs' ) ) {
3903 # Used messages to make sure grep find them:
3904 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3905 if ( $level && $format ) {
3906 $note =
"<div id='mw-{$level}{$format}preview'>" .
3907 $this->context->msg(
"{$level}{$format}preview" )->text() .
3908 ' ' . $continueEditing .
"</div>";
3912 # If we're adding a comment, we need to show the
3913 # summary as the headline
3914 if ( $this->section ===
"new" && $this->summary !==
"" ) {
3915 $content = $content->addSectionHeader( $this->summary );
3918 $hook_args = [ $this, &$content ];
3919 Hooks::run(
'EditPageGetPreviewContent', $hook_args );
3922 $parserOutput = $parserResult[
'parserOutput'];
3923 $previewHTML = $parserResult[
'html'];
3924 $this->mParserOutput = $parserOutput;
3925 $out->addParserOutputMetadata( $parserOutput );
3927 if (
count( $parserOutput->getWarnings() ) ) {
3928 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
3932 $m = $this->context->msg(
3933 'content-failed-to-parse',
3934 $this->contentModel,
3935 $this->contentFormat,
3938 $note .=
"\n\n" . $m->parse();
3942 if ( $this->isConflict ) {
3943 $conflict =
'<h2 id="mw-previewconflict">'
3944 . $this->context->msg(
'previewconflict' )->escaped() .
"</h2>\n";
3946 $conflict =
'<hr />';
3949 $previewhead =
"<div class='previewnote'>\n" .
3950 '<h2 id="mw-previewheader">' . $this->context->msg(
'preview' )->escaped() .
"</h2>" .
3951 $out->parse( $note,
true,
true ) . $conflict .
"</div>\n";
3953 $pageViewLang = $this->mTitle->getPageViewLanguage();
3954 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3955 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3962 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3963 $stats->increment(
'edit.failures.' . $failureType );
3971 $parserOptions = $this->
page->makeParserOptions( $this->context );
3972 $parserOptions->setIsPreview(
true );
3973 $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !==
'' );
3974 $parserOptions->enableLimitReport();
3975 return $parserOptions;
3988 $user = $this->context->getUser();
3991 $scopedCallback = $parserOptions->setupFakeRevision(
3992 $this->mTitle, $pstContent,
$user );
3993 $parserOutput = $pstContent->getParserOutput( $this->mTitle,
null, $parserOptions );
3994 ScopedCallback::consume( $scopedCallback );
3995 $parserOutput->setEditSectionTokens(
false );
3997 'parserOutput' => $parserOutput,
3998 'html' => $parserOutput->getText() ];
4005 if ( $this->preview || $this->section !=
'' ) {
4007 if ( !isset( $this->mParserOutput ) ) {
4010 foreach ( $this->mParserOutput->getTemplates()
as $ns =>
$template ) {
4017 return $this->mTitle->getTemplateLinksFrom();
4033 $showSignature =
true;
4049 'id' =>
'mw-editbutton-bold',
4051 'close' =>
'\'\
'\'',
4052 'sample' =>
wfMessage(
'bold_sample' )->text(),
4053 'tip' =>
wfMessage(
'bold_tip' )->text(),
4056 'id' =>
'mw-editbutton-italic',
4059 'sample' =>
wfMessage(
'italic_sample' )->text(),
4060 'tip' =>
wfMessage(
'italic_tip' )->text(),
4063 'id' =>
'mw-editbutton-link',
4066 'sample' =>
wfMessage(
'link_sample' )->text(),
4067 'tip' =>
wfMessage(
'link_tip' )->text(),
4070 'id' =>
'mw-editbutton-extlink',
4073 'sample' =>
wfMessage(
'extlink_sample' )->text(),
4074 'tip' =>
wfMessage(
'extlink_tip' )->text(),
4077 'id' =>
'mw-editbutton-headline',
4080 'sample' =>
wfMessage(
'headline_sample' )->text(),
4081 'tip' =>
wfMessage(
'headline_tip' )->text(),
4083 $imagesAvailable ? [
4084 'id' =>
'mw-editbutton-image',
4087 'sample' =>
wfMessage(
'image_sample' )->text(),
4088 'tip' =>
wfMessage(
'image_tip' )->text(),
4090 $imagesAvailable ? [
4091 'id' =>
'mw-editbutton-media',
4094 'sample' =>
wfMessage(
'media_sample' )->text(),
4095 'tip' =>
wfMessage(
'media_tip' )->text(),
4098 'id' =>
'mw-editbutton-nowiki',
4099 'open' =>
"<nowiki>",
4100 'close' =>
"</nowiki>",
4101 'sample' =>
wfMessage(
'nowiki_sample' )->text(),
4102 'tip' =>
wfMessage(
'nowiki_tip' )->text(),
4105 'id' =>
'mw-editbutton-signature',
4106 'open' =>
wfMessage(
'sig-text',
'~~~~' )->inContentLanguage()->text(),
4109 'tip' =>
wfMessage(
'sig_tip' )->text(),
4112 'id' =>
'mw-editbutton-hr',
4113 'open' =>
"\n----\n",
4120 $script =
'mw.loader.using("mediawiki.toolbar", function () {';
4121 foreach ( $toolarray
as $tool ) {
4141 'mw.toolbar.addButton',
4143 ResourceLoader::inDebugMode()
4149 $toolbar =
'<div id="toolbar"></div>';
4151 if (
Hooks::run(
'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4154 $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
4181 $user = $this->context->getUser();
4183 if ( !$this->isNew &&
$user->isAllowed(
'minoredit' ) ) {
4184 $checkboxes[
'wpMinoredit'] = [
4185 'id' =>
'wpMinoredit',
4186 'label-message' =>
'minoredit',
4188 'tooltip' =>
'minoredit',
4189 'label-id' =>
'mw-editpage-minoredit',
4190 'legacy-name' =>
'minor',
4191 'default' => $checked[
'minor'],
4195 if (
$user->isLoggedIn() ) {
4196 $checkboxes[
'wpWatchthis'] = [
4197 'id' =>
'wpWatchthis',
4198 'label-message' =>
'watchthis',
4200 'tooltip' =>
'watch',
4201 'label-id' =>
'mw-editpage-watch',
4202 'legacy-name' =>
'watch',
4203 'default' => $checked[
'watch'],
4208 Hooks::run(
'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4229 if ( !$this->isNew ) {
4230 $checkboxes[
'minor'] =
'';
4232 $checkboxes[
'watch'] =
'';
4236 $label = $this->context->msg(
$options[
'label-message'] )->parse();
4244 if ( isset(
$options[
'tooltip'] ) ) {
4245 $attribs[
'accesskey'] = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4248 if ( isset(
$options[
'title-message'] ) ) {
4249 $labelAttribs[
'title'] = $this->context->msg(
$options[
'title-message'] )->text();
4251 if ( isset(
$options[
'label-id'] ) ) {
4252 $labelAttribs[
'id'] =
$options[
'label-id'];
4257 Xml::tags(
'label', $labelAttribs, $label );
4260 $checkboxHtml =
Html::rawElement(
'div', [
'class' =>
'mw-ui-checkbox' ], $checkboxHtml );
4263 $checkboxes[ $legacyName ] = $checkboxHtml;
4268 Hooks::run(
'EditPageBeforeEditChecks', [ &$editPage, &$checkboxes, &
$tabindex ],
'1.29' );
4306 if ( isset(
$options[
'tooltip'] ) ) {
4307 $accesskey = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4310 if ( isset(
$options[
'title-message'] ) ) {
4311 $title = $this->context->msg(
$options[
'title-message'] )->text();
4314 $checkboxes[ $legacyName ] =
new OOUI\FieldLayout(
4315 new OOUI\CheckboxInputWidget( [
4317 'accessKey' => $accesskey,
4322 'infusable' =>
true,
4325 'align' =>
'inline',
4326 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
$options[
'label-message'] )->parse() ),
4336 $legacyCheckboxes = [];
4337 if ( !$this->isNew ) {
4338 $legacyCheckboxes[
'minor'] =
'';
4340 $legacyCheckboxes[
'watch'] =
'';
4342 foreach ( $checkboxes
as $name => $oouiLayout ) {
4347 Hooks::run(
'EditPageBeforeEditChecks', [ &$ep, &$legacyCheckboxes, &
$tabindex ],
'1.29' );
4350 if (
$html && !isset( $checkboxes[
$name] ) ) {
4351 $checkboxes[
$name] =
new OOUI\Widget( [
'content' =>
new OOUI\HtmlSnippet(
$html ) ] );
4366 $this->context->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4369 $newPage = !$this->mTitle->exists();
4371 if ( $labelAsPublish ) {
4372 $buttonLabelKey = $newPage ?
'publishpage' :
'publishchanges';
4374 $buttonLabelKey = $newPage ?
'savearticle' :
'savechanges';
4377 return $buttonLabelKey;
4398 $saveConfig = OOUI\Element::configFromHtmlAttributes(
$attribs );
4399 $buttons[
'save'] =
new OOUI\ButtonInputWidget( [
4400 'id' =>
'wpSaveWidget',
4401 'inputId' =>
'wpSave',
4403 'useInputTag' =>
true,
4404 'flags' => [
'constructive',
'primary' ],
4405 'label' => $buttonLabel,
4406 'infusable' =>
true,
4413 'name' =>
'wpPreview',
4417 $previewConfig = OOUI\Element::configFromHtmlAttributes(
$attribs );
4418 $buttons[
'preview'] =
new OOUI\ButtonInputWidget( [
4419 'id' =>
'wpPreviewWidget',
4420 'inputId' =>
'wpPreview',
4422 'useInputTag' =>
true,
4423 'label' => $this->context->msg(
'showpreview' )->text(),
4424 'infusable' =>
true,
4428 ] + $previewConfig );
4435 $diffConfig = OOUI\Element::configFromHtmlAttributes(
$attribs );
4436 $buttons[
'diff'] =
new OOUI\ButtonInputWidget( [
4437 'id' =>
'wpDiffWidget',
4438 'inputId' =>
'wpDiff',
4440 'useInputTag' =>
true,
4441 'label' => $this->context->msg(
'showdiff' )->text(),
4442 'infusable' =>
true,
4460 $out = $this->context->getOutput();
4461 $out->prepareErrorPage( $this->context->msg(
'nosuchsectiontitle' ) );
4463 $res = $this->context->msg(
'nosuchsectiontext', $this->section )->parseAsBlock();
4470 $out->returnToMain(
false, $this->mTitle );
4481 if ( is_array( $match ) ) {
4482 $match = $this->context->getLanguage()->listToText( $match );
4484 $out = $this->context->getOutput();
4485 $out->prepareErrorPage( $this->context->msg(
'spamprotectiontitle' ) );
4487 $out->addHTML(
'<div id="spamprotected">' );
4488 $out->addWikiMsg(
'spamprotectiontext' );
4492 $out->addHTML(
'</div>' );
4494 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
4497 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
4514 return rtrim(
$request->getText( $field ) );
4534 $out = $this->context->getOutput();
4535 $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4536 if (
count( $editNotices ) ) {
4537 $out->addHTML( implode(
"\n", $editNotices ) );
4539 $msg = $this->context->msg(
'editnotice-notext' );
4540 if ( !$msg->isDisabled() ) {
4542 '<div class="mw-editnotice-notext">'
4543 . $msg->parseAsBlock()
4554 if ( $this->mTitle->isTalkPage() ) {
4555 $this->context->getOutput()->addWikiMsg(
'talkpagetext' );
4563 if ( $this->contentLength ===
false ) {
4564 $this->contentLength = strlen( $this->textbox1 );
4567 $out = $this->context->getOutput();
4568 $lang = $this->context->getLanguage();
4569 $maxArticleSize = $this->context->getConfig()->get(
'MaxArticleSize' );
4570 if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4571 $out->wrapWikiMsg(
"<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4574 $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4575 $lang->formatNum( $maxArticleSize )
4579 if ( !$this->context->msg(
'longpage-hint' )->isDisabled() ) {
4580 $out->wrapWikiMsg(
"<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4583 $lang->formatSize( strlen( $this->textbox1 ) ),
4584 strlen( $this->textbox1 )
4595 $out = $this->context->getOutput();
4596 if ( $this->mTitle->isProtected(
'edit' ) &&
4599 # Is the title semi-protected?
4600 if ( $this->mTitle->isSemiProtected() ) {
4601 $noticeMsg =
'semiprotectedpagewarning';
4603 # Then it must be protected based on static groups (regular)
4604 $noticeMsg =
'protectedpagewarning';
4607 [
'lim' => 1,
'msgKey' => [ $noticeMsg ] ] );
4609 if ( $this->mTitle->isCascadeProtected() ) {
4610 # Is this page under cascading protection from some source pages?
4612 list( $cascadeSources, ) = $this->mTitle->getCascadeProtectionSources();
4613 $notice =
"<div class='mw-cascadeprotectedwarning'>\n$1\n";
4614 $cascadeSourcesCount =
count( $cascadeSources );
4615 if ( $cascadeSourcesCount > 0 ) {
4616 # Explain, and list the titles responsible
4617 foreach ( $cascadeSources
as $page ) {
4618 $notice .=
'* [[:' .
$page->getPrefixedText() .
"]]\n";
4621 $notice .=
'</div>';
4622 $out->wrapWikiMsg( $notice, [
'cascadeprotectedwarning', $cascadeSourcesCount ] );
4624 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions(
'create' ) ) {
4627 'showIfEmpty' =>
false,
4628 'msgKey' => [
'titleprotectedwarning' ],
4629 'wrap' =>
"<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4639 "<div class='mw-explainconflict'>\n$1\n</div>",
4640 [
'explainconflict', $this->context->msg( $this->getSubmitButtonLabel() )->
text() ]
4667 $class =
'mw-editfont-' .
$user->getOption(
'editfont' );
4669 if ( isset(
$attribs[
'class'] ) ) {
4670 if ( is_string(
$attribs[
'class'] ) ) {
4672 } elseif ( is_array(
$attribs[
'class'] ) ) {
4679 $pageLang = $this->mTitle->getPageLanguage();
4680 $attribs[
'lang'] = $pageLang->getHtmlCode();
4681 $attribs[
'dir'] = $pageLang->getDir();
4692 if ( strval( $wikitext ) !==
'' ) {
4717 $userAgent = $this->context->getRequest()->getHeader(
'User-Agent' );
4718 if ( $userAgent && preg_match(
'/MSIE|Edge/', $userAgent ) ) {
4720 return $wgParser->guessLegacySectionNameFromWikiText( $text );
4723 return $wgParser->guessSectionNameFromWikiText( $text );