25 use Wikimedia\ScopedCallback;
375 # Placeholders for text injection by hooks (must be HTML)
376 # extensions should take care to _append_ to the present value
417 $this->mTitle =
$article->getTitle();
418 $this->context =
$article->getContext();
420 $this->contentModel = $this->mTitle->getContentModel();
423 $this->contentFormat =
$handler->getDefaultFormat();
455 $this->mContextTitle =
$title;
466 if ( is_null( $this->mContextTitle ) ) {
482 return $this->enableApiEditOverride ===
true ||
493 $this->enableApiEditOverride = $enableOverride;
514 if ( !
Hooks::run(
'AlternateEdit', [ $this ] ) ) {
518 wfDebug( __METHOD__ .
": enter\n" );
521 if (
$wgRequest->getBool(
'redlink' ) && $this->mTitle->exists() ) {
522 $wgOut->redirect( $this->mTitle->getFullURL() );
527 $this->firsttime =
false;
532 $this->preview =
true;
536 $this->formtype =
'save';
537 } elseif ( $this->preview ) {
538 $this->formtype =
'preview';
539 } elseif ( $this->
diff ) {
540 $this->formtype =
'diff';
541 }
else { #
First time through
542 $this->firsttime =
true;
544 $this->formtype =
'preview';
546 $this->formtype =
'initial';
552 wfDebug( __METHOD__ .
": User can't edit\n" );
557 $user->spreadAnyEditBlock();
565 $revision = $this->mArticle->getRevisionFetched();
572 if ( $this->undidRev ) {
574 $prevRev = $undidRevObj ? $undidRevObj->getPrevious() :
null;
576 if ( !$this->undidRev
583 'contentmodelediterror',
584 $revision->getContentModel(),
592 $this->isConflict =
false;
594 $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
595 $this->isCssSubpage = $this->mTitle->isCssSubpage();
596 $this->isJsSubpage = $this->mTitle->isJsSubpage();
600 # Show applicable editing introductions
601 if ( $this->formtype ==
'initial' || $this->firsttime ) {
605 # Attempt submission here. This will check for edit conflicts,
606 # and redundantly check for locked database, blocked IPs, etc.
607 # that edit() already checked just in case someone tries to sneak
608 # in the back door with a hand-edited submission URL.
610 if (
'save' == $this->formtype ) {
611 $resultDetails =
null;
618 # First time through: get contents, set time for conflict
620 if (
'initial' == $this->formtype || $this->firsttime ) {
626 if ( !$this->mTitle->getArticleID() ) {
627 Hooks::run(
'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
629 Hooks::run(
'EditFormInitialText', [ $this ] );
644 $permErrors = $this->mTitle->getUserPermissionsErrors(
'edit',
$wgUser, $rigor );
645 # Can this title be created?
646 if ( !$this->mTitle->exists() ) {
647 $permErrors = array_merge(
650 $this->mTitle->getUserPermissionsErrors(
'create',
$wgUser, $rigor ),
655 # Ignore some permissions errors when a user is just previewing/viewing diffs
657 foreach ( $permErrors
as $error ) {
658 if ( ( $this->preview || $this->
diff )
659 && ( $error[0] ==
'blockedtext' || $error[0] ==
'autoblockedtext' )
689 $wgOut->redirect( $this->mTitle->getFullURL() );
695 # Use the normal message if there's nothing to display
697 $action = $this->mTitle->exists() ?
'edit' :
698 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
704 $wgOut->formatPermissionsErrorMessage( $permErrors,
'edit' )
718 $wgOut->setRobotPolicy(
'noindex,nofollow' );
719 $wgOut->setPageTitle( $this->context->msg(
721 $this->getContextTitle()->getPrefixedText()
724 $wgOut->addHTML( $this->editFormPageTop );
725 $wgOut->addHTML( $this->editFormTextTop );
727 if ( $errorMessage !==
'' ) {
728 $wgOut->addWikiText( $errorMessage );
729 $wgOut->addHTML(
"<hr />\n" );
732 # If the user made changes, preserve them when showing the markup
733 # (This happens when a user is blocked during edit, for instance)
734 if ( !$this->firsttime ) {
736 $wgOut->addWikiMsg(
'viewyourtext' );
741 # Serialize using the default format if the content model is not supported
742 # (e.g. for an old revision with a different model)
745 $wgOut->addWikiMsg(
'viewsourcetext' );
748 $wgOut->addHTML( $this->editFormTextBeforeContent );
749 $this->
showTextbox( $text,
'wpTextbox1', [
'readonly' ] );
750 $wgOut->addHTML( $this->editFormTextAfterContent );
754 $wgOut->addModules(
'mediawiki.action.edit.collapsibleFooter' );
756 $wgOut->addHTML( $this->editFormTextBottom );
757 if ( $this->mTitle->exists() ) {
758 $wgOut->returnToMain(
null, $this->mTitle );
769 if (
$wgRequest->getVal(
'preview' ) ==
'yes' ) {
772 } elseif (
$wgRequest->getVal(
'preview' ) ==
'no' ) {
775 } elseif ( $this->section ==
'new' ) {
778 } elseif ( (
$wgRequest->getVal(
'preload' ) !==
null || $this->mTitle->exists() )
779 &&
$wgUser->getOption(
'previewonfirst' )
783 } elseif ( !$this->mTitle->exists()
784 && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
785 && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
801 if ( $this->mTitle->isCssJsSubpage() ) {
802 $name = $this->mTitle->getSkinFromCssJsSubpage();
803 $skins = array_merge(
807 return !in_array(
$name, $skins )
808 && in_array( strtolower(
$name ), $skins );
823 return $contentHandler->supportsSections();
834 # Section edit can come from either the form or a link
835 $this->section =
$request->getVal(
'wpSection',
$request->getVal(
'section' ) );
838 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
841 $this->isNew = !$this->mTitle->exists() || $this->section ==
'new';
844 # These fields need to be checked for encoding.
845 # Also remove trailing whitespace, but don't remove _initial_
846 # whitespace from the text boxes. This may be significant formatting.
848 if ( !
$request->getCheck(
'wpTextbox2' ) ) {
858 # Truncate for whole multibyte characters
861 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
862 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
864 $this->summary = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->summary );
866 # Treat sectiontitle the same way as summary.
867 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
868 # currently doing double duty as both edit summary and section title. Right now this
869 # is just to allow API edits to work around this limitation, but this should be
870 # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
872 $this->sectiontitle = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->sectiontitle );
874 $this->edittime =
$request->getVal(
'wpEdittime' );
875 $this->editRevId =
$request->getIntOrNull(
'editRevId' );
876 $this->starttime =
$request->getVal(
'wpStarttime' );
883 $this->scrolltop =
$request->getIntOrNull(
'wpScrolltop' );
885 if ( $this->textbox1 ===
'' &&
$request->getVal(
'wpTextbox1' ) === null ) {
889 $this->incompleteForm =
true;
897 $this->incompleteForm = ( !
$request->getVal(
'wpUltimateParam' )
898 && is_null( $this->edittime ) );
900 if ( $this->incompleteForm ) {
901 # If the form is incomplete, force to preview.
902 wfDebug( __METHOD__ .
": Form data appears to be incomplete\n" );
903 wfDebug(
"POST DATA: " . var_export( $_POST,
true ) .
"\n" );
904 $this->preview =
true;
906 $this->preview =
$request->getCheck(
'wpPreview' );
914 # Some browsers will not report any submit button
915 # if the user hits enter in the comment box.
916 # The unmarked state will be assumed to be a save,
917 # if the form seems otherwise complete.
918 wfDebug( __METHOD__ .
": Passed token check.\n" );
919 } elseif ( $this->
diff ) {
920 # Failed token check, but only requested "Show Changes".
921 wfDebug( __METHOD__ .
": Failed token check; Show Changes requested.\n" );
923 # Page might be a hack attempt posted from
924 # an external site. Preview instead of saving.
925 wfDebug( __METHOD__ .
": Failed token check; forcing preview\n" );
926 $this->preview =
true;
930 if ( !preg_match(
'/^\d{14}$/', $this->edittime ) ) {
931 $this->edittime =
null;
934 if ( !preg_match(
'/^\d{14}$/', $this->starttime ) ) {
935 $this->starttime =
null;
938 $this->recreate =
$request->getCheck(
'wpRecreate' );
940 $this->minoredit =
$request->getCheck(
'wpMinoredit' );
941 $this->watchthis =
$request->getCheck(
'wpWatchthis' );
943 # Don't force edit summaries when a user is editing their own user or talk page
945 && $this->mTitle->getText() ==
$wgUser->getName()
947 $this->allowBlankSummary =
true;
949 $this->allowBlankSummary =
$request->getBool(
'wpIgnoreBlankSummary' )
950 || !
$wgUser->getOption(
'forceeditsummary' );
953 $this->autoSumm =
$request->getText(
'wpAutoSummary' );
955 $this->allowBlankArticle =
$request->getBool(
'wpIgnoreBlankArticle' );
956 $this->allowSelfRedirect =
$request->getBool(
'wpIgnoreSelfRedirect' );
960 $this->changeTags = [];
962 $this->changeTags = array_filter( array_map(
'trim', explode(
',',
966 # Not a posted form? Start with nothing.
967 wfDebug( __METHOD__ .
": Not a posted form.\n" );
968 $this->textbox1 =
'';
970 $this->sectiontitle =
'';
971 $this->edittime =
'';
972 $this->editRevId =
null;
975 $this->preview =
false;
978 $this->minoredit =
false;
980 $this->watchthis =
$request->getBool(
'watchthis',
false );
981 $this->recreate =
false;
985 if ( $this->section ==
'new' &&
$request->getVal(
'preloadtitle' ) ) {
986 $this->sectiontitle =
$request->getVal(
'preloadtitle' );
988 $this->summary =
$request->getVal(
'preloadtitle' );
989 } elseif ( $this->section !=
'new' &&
$request->getVal(
'summary' ) ) {
990 $this->summary =
$request->getText(
'summary' );
991 if ( $this->summary !==
'' ) {
992 $this->hasPresetSummary =
true;
996 if (
$request->getVal(
'minor' ) ) {
997 $this->minoredit =
true;
1001 $this->oldid =
$request->getInt(
'oldid' );
1002 $this->parentRevId =
$request->getInt(
'parentRevId' );
1004 $this->bot =
$request->getBool(
'bot',
true );
1005 $this->nosummary =
$request->getBool(
'nosummary' );
1008 $this->contentModel =
$request->getText(
'model', $this->contentModel );
1010 $this->contentFormat =
$request->getText(
'format', $this->contentFormat );
1016 'editpage-invalidcontentmodel-title',
1017 'editpage-invalidcontentmodel-text',
1022 if ( !
$handler->isSupportedFormat( $this->contentFormat ) ) {
1024 'editpage-notsupportedcontentformat-title',
1025 'editpage-notsupportedcontentformat-text',
1039 $this->editintro =
$request->getText(
'editintro',
1041 $this->section ===
'new' ?
'MediaWiki:addsection-editintro' :
'' );
1068 $this->edittime = $this->
page->getTimestamp();
1069 $this->editRevId = $this->
page->getLatest();
1078 # Sort out the "watch" checkbox
1079 if ( $wgUser->getOption(
'watchdefault' ) ) {
1081 $this->watchthis =
true;
1082 } elseif (
$wgUser->getOption(
'watchcreations' ) && !$this->mTitle->exists() ) {
1084 $this->watchthis =
true;
1085 } elseif (
$wgUser->isWatched( $this->mTitle ) ) {
1087 $this->watchthis =
true;
1090 $this->minoredit =
true;
1092 if ( $this->textbox1 ===
false ) {
1112 if ( !$this->mTitle->exists() || $this->section ==
'new' ) {
1113 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && $this->section !=
'new' ) {
1114 # If this is a system message, get the default text.
1115 $msg = $this->mTitle->getDefaultMessageText();
1120 # If requested, preload some text.
1123 $this->section ===
'new' ?
'MediaWiki:addsection-preload' :
'' );
1130 if ( $this->section !=
'' ) {
1133 $content = $orig ? $orig->getSection( $this->section ) :
null;
1139 $undoafter =
$wgRequest->getInt(
'undoafter' );
1142 if ( $undo > 0 && $undoafter > 0 ) {
1146 # Sanity check, make sure it's the right page,
1147 # the revisions exist and they were not deleted.
1148 # Otherwise, $content will be left as-is.
1149 if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1153 $content = $this->
page->getUndoContent( $undorev, $oldrev );
1156 # Warn the user that something went wrong
1157 $undoMsg =
'failure';
1161 $newContent =
$content->preSaveTransform( $this->mTitle,
$wgUser, $popts );
1162 if ( $newContent->getModel() !== $oldContent->getModel() ) {
1167 $this->contentModel = $newContent->getModel();
1168 $this->contentFormat = $oldrev->getContentFormat();
1171 if ( $newContent->equals( $oldContent ) ) {
1172 # Tell the user that the undo results in no change,
1173 # i.e. the revisions were already undone.
1174 $undoMsg =
'nochange';
1177 # Inform the user of our success and set an automatic edit summary
1178 $undoMsg =
'success';
1180 # If we just undid one rev, use an autosummary
1181 $firstrev = $oldrev->getNext();
1182 if ( $firstrev && $firstrev->getId() == $undo ) {
1183 $userText = $undorev->getUserText();
1184 if ( $userText ===
'' ) {
1185 $undoSummary = $this->context->msg(
1186 'undo-summary-username-hidden',
1188 )->inContentLanguage()->text();
1190 $undoSummary = $this->context->msg(
1194 )->inContentLanguage()->text();
1196 if ( $this->summary ===
'' ) {
1197 $this->summary = $undoSummary;
1199 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1202 $this->undidRev = $undo;
1204 $this->formtype =
'diff';
1215 $class = ( $undoMsg ==
'success' ?
'' :
'error ' ) .
"mw-undo-{$undoMsg}";
1216 $this->editFormPageTop .=
$wgOut->parse(
"<div class=\"{$class}\">" .
1217 $this->context->msg(
'undo-' . $undoMsg )->plain() .
'</div>',
true,
true );
1245 if ( $this->section ==
'new' ) {
1248 $revision = $this->mArticle->getRevisionFetched();
1249 if ( $revision ===
null ) {
1250 if ( !$this->contentModel ) {
1251 $this->contentModel = $this->
getTitle()->getContentModel();
1255 return $handler->makeEmptyContent();
1274 if ( $this->parentRevId ) {
1277 return $this->mArticle->getRevIdFetched();
1294 if ( !$this->contentModel ) {
1295 $this->contentModel = $this->
getTitle()->getContentModel();
1299 return $handler->makeEmptyContent();
1300 } elseif ( !$this->undidRev ) {
1305 $logger = LoggerFactory::getInstance(
'editpage' );
1306 if ( $this->contentModel !==
$rev->getContentModel() ) {
1307 $logger->warning(
"Overriding content model from current edit {prev} to {new}", [
1308 'prev' => $this->contentModel,
1309 'new' =>
$rev->getContentModel(),
1310 'title' => $this->
getTitle()->getPrefixedDBkey(),
1311 'method' => __METHOD__
1313 $this->contentModel =
$rev->getContentModel();
1318 if ( !
$content->isSupportedFormat( $this->contentFormat ) ) {
1319 $logger->warning(
"Current revision content format unsupported. Overriding {prev} to {new}", [
1321 'prev' => $this->contentFormat,
1322 'new' =>
$rev->getContentFormat(),
1323 'title' => $this->
getTitle()->getPrefixedDBkey(),
1324 'method' => __METHOD__
1326 $this->contentFormat =
$rev->getContentFormat();
1357 if ( !empty( $this->mPreloadContent ) ) {
1363 if ( $preload ===
'' ) {
1364 return $handler->makeEmptyContent();
1368 # Check for existence to avoid getting MediaWiki:Noarticletext
1371 return $handler->makeEmptyContent();
1380 return $handler->makeEmptyContent();
1390 return $handler->makeEmptyContent();
1396 if ( !$converted ) {
1398 wfDebug(
"Attempt to preload incompatible content: " .
1399 "can't convert " .
$content->getModel() .
1402 return $handler->makeEmptyContent();
1420 $token =
$request->getVal(
'wpEditToken' );
1421 $this->mTokenOk =
$wgUser->matchEditToken( $token );
1422 $this->mTokenOkExceptSuffix =
$wgUser->matchEditTokenNoSuffix( $token );
1443 $revisionId = $this->
page->getLatest();
1444 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1447 if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1449 } elseif ( $this->oldid ) {
1454 $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, [
1455 'httpOnly' =>
false,
1468 # Allow bots to exempt some edits from bot flagging
1472 Hooks::run(
'EditPage::attemptSave:after', [ $this,
$status, $resultDetails ] );
1493 if (
$status->value == self::AS_SUCCESS_UPDATE
1494 ||
$status->value == self::AS_SUCCESS_NEW_ARTICLE
1496 $this->didSave =
true;
1497 if ( !$resultDetails[
'nullEdit'] ) {
1505 $extraQueryRedirect =
$request->getVal(
'wpExtraQueryRedirect' );
1525 $wgOut->addWikiText(
'<div class="error">' .
"\n" .
$status->getWikiText() .
'</div>' );
1529 $query = $resultDetails[
'redirect'] ?
'redirect=no' :
'';
1530 if ( $extraQueryRedirect ) {
1532 $query = $extraQueryRedirect;
1537 $anchor = isset( $resultDetails[
'sectionanchor'] ) ? $resultDetails[
'sectionanchor'] :
'';
1538 $wgOut->redirect( $this->mTitle->getFullURL(
$query ) . $anchor );
1543 $sectionanchor = $resultDetails[
'sectionanchor'];
1547 'ArticleUpdateBeforeRedirect',
1548 [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1551 if ( $resultDetails[
'redirect'] ) {
1552 if ( $extraQuery ==
'' ) {
1553 $extraQuery =
'redirect=no';
1555 $extraQuery =
'redirect=no&' . $extraQuery;
1558 if ( $extraQueryRedirect ) {
1559 if ( $extraQuery ===
'' ) {
1560 $extraQuery = $extraQueryRedirect;
1562 $extraQuery = $extraQuery .
'&' . $extraQueryRedirect;
1566 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1591 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
1602 $this->hookError =
'<div class="error">' .
"\n" .
$status->getWikiText() .
1620 [ $this,
$content, &$this->hookError, $this->summary ],
1623 # Error messages etc. could be handled within the hook...
1624 $status->fatal(
'hookaborted' );
1627 } elseif ( $this->hookError !=
'' ) {
1628 # ...or the hook could be expecting us to produce an error
1629 $status->fatal(
'hookaborted' );
1639 # Error messages etc. could be handled within the hook...
1641 $status->fatal(
'hookaborted' );
1647 $this->hookError =
$status->getWikiText();
1654 } elseif ( !
$status->isOK() ) {
1655 # ...or the hook could be expecting us to produce an error
1657 $this->hookError =
$status->getWikiText();
1658 $status->fatal(
'hookaborted' );
1675 if ( $this->sectiontitle !==
'' ) {
1676 $sectionanchor =
$wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1680 if ( $this->summary ===
'' ) {
1681 $cleanSectionTitle =
$wgParser->stripSectionName( $this->sectiontitle );
1682 return $this->context->msg(
'newsectionsummary' )
1683 ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1685 } elseif ( $this->summary !==
'' ) {
1686 $sectionanchor =
$wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1687 # This is a new section, so create a link to the new section
1688 # in the revision summary.
1689 $cleanSummary =
$wgParser->stripSectionName( $this->summary );
1690 return $this->context->msg(
'newsectionsummary' )
1691 ->rawParams( $cleanSummary )->inContentLanguage()->text();
1722 global $wgContentHandlerUseDB;
1726 if ( !
Hooks::run(
'EditPage::attemptSave', [ $this ] ) ) {
1727 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving\n" );
1728 $status->fatal(
'hookaborted' );
1734 if ( $spam !==
'' ) {
1739 $this->mTitle->getPrefixedText() .
1740 '" submitted bogus field "' .
1744 $status->fatal(
'spamprotectionmatch',
false );
1750 # Construct Content object
1754 'content-failed-to-parse',
1755 $this->contentModel,
1756 $this->contentFormat,
1763 # Check image redirect
1764 if ( $this->mTitle->getNamespace() ==
NS_FILE &&
1765 $textbox_content->isRedirect() &&
1766 !
$wgUser->isAllowed(
'upload' )
1776 if ( $match ===
false && $this->section ==
'new' ) {
1777 # $wgSpamRegex is enforced on this new heading/summary because, unlike
1778 # regular summaries, it is added to the actual wikitext.
1779 if ( $this->sectiontitle !==
'' ) {
1780 # This branch is taken when the API is used with the 'sectiontitle' parameter.
1783 # This branch is taken when the "Add Topic" user interface is used, or the API
1784 # is used with the 'summary' parameter.
1788 if ( $match ===
false ) {
1791 if ( $match !==
false ) {
1794 $pdbk = $this->mTitle->getPrefixedDBkey();
1795 $match = str_replace(
"\n",
'', $match );
1796 wfDebugLog(
'SpamRegex',
"$ip spam regex hit [[$pdbk]]: \"$match\"" );
1797 $status->fatal(
'spamprotectionmatch', $match );
1803 [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1805 # Error messages etc. could be handled within the hook...
1806 $status->fatal(
'hookaborted' );
1809 } elseif ( $this->hookError !=
'' ) {
1810 # ...or the hook could be expecting us to produce an error
1811 $status->fatal(
'hookaborted' );
1816 if (
$wgUser->isBlockedFrom( $this->mTitle,
false ) ) {
1819 $wgUser->spreadAnyEditBlock();
1821 # Check block state against master, thus 'false'.
1822 $status->setResult(
false, self::AS_BLOCKED_PAGE_FOR_USER );
1826 $this->contentLength = strlen( $this->textbox1 );
1829 $this->tooBig =
true;
1830 $status->setResult(
false, self::AS_CONTENT_TOO_BIG );
1834 if ( !
$wgUser->isAllowed(
'edit' ) ) {
1836 $status->setResult(
false, self::AS_READ_ONLY_PAGE_ANON );
1839 $status->fatal(
'readonlytext' );
1845 $changingContentModel =
false;
1846 if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1847 if ( !$wgContentHandlerUseDB ) {
1848 $status->fatal(
'editpage-cannot-use-custom-model' );
1851 } elseif ( !
$wgUser->isAllowed(
'editcontentmodel' ) ) {
1852 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1858 if ( !$titleWithNewContentModel->userCan(
'editcontentmodel',
$wgUser )
1859 || !$titleWithNewContentModel->userCan(
'edit',
$wgUser )
1861 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1865 $changingContentModel =
true;
1866 $oldContentModel = $this->mTitle->getContentModel();
1869 if ( $this->changeTags ) {
1872 if ( !$changeTagsStatus->isOK() ) {
1874 return $changeTagsStatus;
1879 $status->fatal(
'readonlytext' );
1883 if (
$wgUser->pingLimiter() ||
$wgUser->pingLimiter(
'linkpurge', 0 )
1884 || ( $changingContentModel &&
$wgUser->pingLimiter(
'editcontentmodel' ) )
1886 $status->fatal(
'actionthrottledtext' );
1891 # If the article has been deleted while editing, don't save it without
1894 $status->setResult(
false, self::AS_ARTICLE_WAS_DELETED );
1898 # Load the page data from the master. If anything changes in the meantime,
1899 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1900 $this->
page->loadPageData(
'fromdbmaster' );
1901 $new = !$this->
page->exists();
1905 if ( !$this->mTitle->userCan(
'create',
$wgUser ) ) {
1906 $status->fatal(
'nocreatetext' );
1908 wfDebug( __METHOD__ .
": no create permission\n" );
1915 $defaultMessageText = $this->mTitle->getDefaultMessageText();
1916 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI && $defaultMessageText !==
false ) {
1917 $defaultText = $defaultMessageText;
1922 if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1923 $this->blankArticle =
true;
1924 $status->fatal(
'blankarticle' );
1925 $status->setResult(
false, self::AS_BLANK_ARTICLE );
1935 $result[
'sectionanchor'] =
'';
1936 if ( $this->section ==
'new' ) {
1937 if ( $this->sectiontitle !==
'' ) {
1940 } elseif ( $this->summary !==
'' ) {
1951 # Article exists. Check for edit conflict.
1955 $latest = $this->
page->getLatest();
1957 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1961 || ( $this->editRevId !==
null && $this->editRevId != $latest )
1963 $this->isConflict =
true;
1964 if ( $this->section ==
'new' ) {
1965 if ( $this->
page->getUserText() == $wgUser->getName() &&
1972 .
": duplicate new section submission; trigger edit conflict!\n" );
1975 $this->isConflict =
false;
1976 wfDebug( __METHOD__ .
": conflict suppressed; new section\n" );
1978 } elseif ( $this->section ==
''
1980 DB_MASTER, $this->mTitle->getArticleID(),
1984 # Suppress edit conflict with self, except for section edits where merging is required.
1985 wfDebug( __METHOD__ .
": Suppressing edit conflict, same user.\n" );
1986 $this->isConflict =
false;
1991 if ( $this->sectiontitle !==
'' ) {
1999 if ( $this->isConflict ) {
2001 .
": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2002 .
" (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2005 if ( $this->editRevId !==
null ) {
2021 wfDebug( __METHOD__ .
": getting section '{$this->section}'\n" );
2030 wfDebug( __METHOD__ .
": activating conflict; section replace failed.\n" );
2031 $this->isConflict =
true;
2033 } elseif ( $this->isConflict ) {
2037 $this->isConflict =
false;
2038 wfDebug( __METHOD__ .
": Suppressing edit conflict, successful merge.\n" );
2040 $this->section =
'';
2042 wfDebug( __METHOD__ .
": Keeping edit conflict, failed merge.\n" );
2046 if ( $this->isConflict ) {
2047 $status->setResult(
false, self::AS_CONFLICT_DETECTED );
2055 if ( $this->section ==
'new' ) {
2057 if ( !$this->allowBlankSummary && trim( $this->summary ) ==
'' ) {
2058 $this->missingSummary =
true;
2059 $status->fatal(
'missingsummary' );
2065 if ( $this->textbox1 ==
'' ) {
2066 $this->missingComment =
true;
2067 $status->fatal(
'missingcommenttext' );
2071 } elseif ( !$this->allowBlankSummary
2076 $this->missingSummary =
true;
2077 $status->fatal(
'missingsummary' );
2083 $sectionanchor =
'';
2084 if ( $this->section ==
'new' ) {
2086 } elseif ( $this->section !=
'' ) {
2087 # Try to get a section anchor from the section source, redirect
2088 # to edited section if header found.
2089 # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2090 # for duplicate heading checking and maybe parsing.
2091 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
2092 # We can't deal with anchors, includes, html etc in the header for now,
2093 # headline would need to be parsed to improve this.
2094 if ( $hasmatch && strlen(
$matches[2] ) > 0 ) {
2098 $result[
'sectionanchor'] = $sectionanchor;
2105 $this->section =
'';
2110 if ( !$this->allowSelfRedirect
2116 if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2117 $this->selfRedirect =
true;
2118 $status->fatal(
'selfredirect' );
2127 $this->tooBig =
true;
2128 $status->setResult(
false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2134 ( ( $this->minoredit && !$this->isNew ) ?
EDIT_MINOR : 0 ) |
2137 $doEditStatus = $this->
page->doEditContent(
2147 if ( !$doEditStatus->isOK() ) {
2151 $errors = $doEditStatus->getErrorsArray();
2152 if ( in_array( $errors[0][0],
2153 [
'edit-gone-missing',
'edit-conflict',
'edit-already-exists' ] )
2155 $this->isConflict =
true;
2159 return $doEditStatus;
2162 $result[
'nullEdit'] = $doEditStatus->hasMessage(
'edit-no-change' );
2165 $wgUser->pingLimiter(
'linkpurge' );
2172 if ( $changingContentModel ) {
2175 $new ?
false : $oldContentModel,
2176 $this->contentModel,
2191 $new = $oldModel ===
false;
2192 $log =
new ManualLogEntry(
'contentmodel', $new ?
'new' :
'change' );
2193 $log->setPerformer(
$user );
2194 $log->setTarget( $this->mTitle );
2195 $log->setComment( $reason );
2196 $log->setParameters( [
2197 '4::oldmodel' => $oldModel,
2198 '5::newmodel' => $newModel
2200 $logid = $log->insert();
2201 $log->publish( $logid );
2210 if ( !
$wgUser->isLoggedIn() ) {
2243 $baseContent = $baseRevision ? $baseRevision->getContent() :
null;
2245 if ( is_null( $baseContent ) ) {
2251 $currentContent = $currentRevision ? $currentRevision->getContent() :
null;
2253 if ( is_null( $currentContent ) ) {
2259 $result =
$handler->merge3( $baseContent, $editContent, $currentContent );
2264 $this->parentRevId = $currentRevision->getId();
2277 if ( !$this->mBaseRevision ) {
2279 $this->mBaseRevision = $this->editRevId
2296 $regexes = (
array)$wgSpamRegex;
2308 global $wgSummarySpamRegex;
2309 $regexes = (
array)$wgSummarySpamRegex;
2319 foreach ( $regexes
as $regex ) {
2321 if ( preg_match( $regex, $text,
$matches ) ) {
2331 $wgOut->addModules(
'mediawiki.action.edit' );
2332 $wgOut->addModuleStyles(
'mediawiki.action.edit.styles' );
2334 if (
$wgUser->getOption(
'showtoolbar' ) ) {
2339 $wgOut->addModules(
'mediawiki.toolbar' );
2342 if (
$wgUser->getOption(
'uselivepreview' ) ) {
2343 $wgOut->addModules(
'mediawiki.action.edit.preview' );
2346 if (
$wgUser->getOption(
'useeditwarning' ) ) {
2347 $wgOut->addModules(
'mediawiki.action.edit.editWarning' );
2350 # Enabled article-related sidebar, toplinks, etc.
2351 $wgOut->setArticleRelated(
true );
2354 if ( $this->isConflict ) {
2355 $msg =
'editconflict';
2356 } elseif ( $contextTitle->exists() && $this->section !=
'' ) {
2357 $msg = $this->section ==
'new' ?
'editingcomment' :
'editingsection';
2359 $msg = $contextTitle->exists()
2361 && $contextTitle->getDefaultMessageText() !==
false
2367 # Use the title defined by DISPLAYTITLE magic word when present
2368 # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2369 # setPageTitle() treats the input as wikitext, which should be safe in either case.
2370 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() :
false;
2371 if ( $displayTitle ===
false ) {
2372 $displayTitle = $contextTitle->getPrefixedText();
2374 $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2375 # Transmit the name of the message to JavaScript for live preview
2376 # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2377 $wgOut->addJsConfigVars( [
2378 'wgEditMessage' => $msg,
2379 'wgAjaxEditStash' => $wgAjaxEditStash,
2388 if ( $this->suppressIntro ) {
2392 $namespace = $this->mTitle->getNamespace();
2395 # Show a warning if editing an interface message
2396 $wgOut->wrapWikiMsg(
"<div class='mw-editinginterface'>\n$1\n</div>",
'editinginterface' );
2397 # If this is a default message (but not css or js),
2398 # show a hint that it is translatable on translatewiki.net
2402 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2403 if ( $defaultMessageText !==
false ) {
2404 $wgOut->wrapWikiMsg(
"<div class='mw-translateinterface'>\n$1\n</div>",
2405 'translateinterface' );
2408 } elseif ( $namespace ==
NS_FILE ) {
2409 # Show a hint to shared repo
2411 if ( $file && !$file->isLocal() ) {
2412 $descUrl = $file->getDescriptionUrl();
2413 # there must be a description url to show a hint to shared repo
2415 if ( !$this->mTitle->exists() ) {
2416 $wgOut->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2417 'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2420 $wgOut->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2421 'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2428 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2429 # Show log extract when the user is currently blocked
2431 $username = explode(
'/', $this->mTitle->getText(), 2 )[0];
2436 $wgOut->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2439 # Show log extract if the user is currently blocked
2447 'showIfEmpty' =>
false,
2449 'blocked-notice-logextract',
2450 $user->getName() # Support GENDER
in notice
2456 # Try to add a custom edit intro, or use the standard one if this is not possible.
2459 $this->context->msg(
'helppage' )->inContentLanguage()->text()
2461 if (
$wgUser->isLoggedIn() ) {
2464 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2473 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2475 'newarticletextanon',
2481 # Give a notice if the user is editing a deleted/moved page...
2482 if ( !$this->mTitle->exists() ) {
2487 'conds' => [
"log_action != 'revision'" ],
2488 'showIfEmpty' =>
false,
2489 'msgKey' => [
'recreate-moveddeleted-warn' ]
2501 if ( $this->editintro ) {
2506 $wgOut->addWikiTextTitleTidy(
2507 '<div class="mw-editintro">{{:' .
$title->getFullText() .
'}}</div>',
2543 return $content->serialize( $this->contentFormat );
2563 if ( $text ===
false || $text ===
null ) {
2568 $this->contentModel, $this->contentFormat );
2588 # need to parse the preview early so that we know which templates are used,
2589 # otherwise users with "show preview after edit box" will get a blank list
2590 # we parse this near the beginning so that setHeaders can do the title
2591 # setting work instead of leaving it in getPreviewText
2592 $previewOutput =
'';
2593 if ( $this->formtype ==
'preview' ) {
2605 $wgOut->addHTML( $this->editFormPageTop );
2607 if (
$wgUser->getOption(
'previewontop' ) ) {
2611 $wgOut->addHTML( $this->editFormTextTop );
2613 $showToolbar =
true;
2615 if ( $this->formtype ==
'save' ) {
2618 $showToolbar =
false;
2620 $wgOut->wrapWikiMsg(
"<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2621 'deletedwhileediting' );
2630 'id' => self::EDITFORM_ID,
2631 'name' => self::EDITFORM_ID,
2634 'enctype' =>
'multipart/form-data'
2638 if ( is_callable( $formCallback ) ) {
2639 wfWarn(
'The $formCallback parameter to ' . __METHOD__ .
'is deprecated' );
2640 call_user_func_array( $formCallback, [ &
$wgOut ] );
2645 Xml::openElement(
'div', [
'id' =>
'antispam-container',
'style' =>
'display: none;' ] )
2648 [
'for' =>
'wpAntispam' ],
2649 $this->context->msg(
'simpleantispam-label' )->parse()
2655 'name' =>
'wpAntispam',
2656 'id' =>
'wpAntispam',
2669 $username = $this->lastDelete->user_name;
2670 $comment = $this->lastDelete->log_comment;
2675 ?
'confirmrecreate-noreason'
2676 :
'confirmrecreate';
2678 '<div class="mw-confirm-recreate">' .
2679 $this->context->msg( $key,
$username,
"<nowiki>$comment</nowiki>" )->parse() .
2680 Xml::checkLabel( $this->context->msg(
'recreate' )->text(),
'wpRecreate',
'wpRecreate',
false,
2687 # When the summary is hidden, also hide them on preview/show changes
2688 if ( $this->nosummary ) {
2692 # If a blank edit summary was previously provided, and the appropriate
2693 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2694 # user being bounced back more than once in the event that a summary
2697 # For a bit more sophisticated detection of blank summaries, hash the
2698 # automatic one and pass that in the hidden field wpAutoSummary.
2699 if ( $this->missingSummary || ( $this->section ==
'new' && $this->nosummary ) ) {
2703 if ( $this->undidRev ) {
2707 if ( $this->selfRedirect ) {
2711 if ( $this->hasPresetSummary ) {
2715 $this->autoSumm = md5(
'' );
2718 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2724 $wgOut->addHTML(
Html::hidden(
'format', $this->contentFormat ) );
2727 if ( $this->section ==
'new' ) {
2732 $wgOut->addHTML( $this->editFormTextBeforeContent );
2734 if ( !$this->isCssJsSubpage && $showToolbar &&
$wgUser->getOption(
'showtoolbar' ) ) {
2738 if ( $this->blankArticle ) {
2742 if ( $this->isConflict ) {
2757 $wgOut->addHTML( $this->editFormTextAfterContent );
2767 $wgOut->addHTML( $this->editFormTextAfterTools .
"\n" );
2775 self::getPreviewLimitReport( $this->mParserOutput ) ) );
2777 $wgOut->addModules(
'mediawiki.action.edit.collapsibleFooter' );
2779 if ( $this->isConflict ) {
2784 $msg = $this->context->msg(
2785 'content-failed-to-parse',
2786 $this->contentModel,
2787 $this->contentFormat,
2790 $wgOut->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
2795 if ( $this->isConflict ) {
2797 } elseif ( $this->preview ) {
2799 } elseif ( $this->
diff ) {
2809 $wgOut->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
2811 if ( !
$wgUser->getOption(
'previewontop' ) ) {
2826 $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2831 if ( $this->preview ) {
2833 } elseif ( $this->section !=
'' ) {
2838 $templateListFormatter->format( $templates,
$type )
2850 preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches );
2864 global $wgAllowUserCss, $wgAllowUserJs;
2866 if ( $this->mTitle->isTalkPage() ) {
2867 $wgOut->addWikiMsg(
'talkpagetext' );
2871 $editNotices = $this->mTitle->getEditNotices( $this->oldid );
2872 if ( count( $editNotices ) ) {
2873 $wgOut->addHTML( implode(
"\n", $editNotices ) );
2875 $msg = $this->context->msg(
'editnotice-notext' );
2876 if ( !$msg->isDisabled() ) {
2878 '<div class="mw-editnotice-notext">'
2879 . $msg->parseAsBlock()
2885 if ( $this->isConflict ) {
2886 $wgOut->wrapWikiMsg(
"<div class='mw-explainconflict'>\n$1\n</div>",
'explainconflict' );
2887 $this->editRevId = $this->
page->getLatest();
2893 $wgOut->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
2897 if ( $this->section !=
'' && $this->section !=
'new' ) {
2898 if ( !$this->summary && !$this->preview && !$this->
diff ) {
2900 if ( $sectionTitle !==
false ) {
2901 $this->summary =
"/* $sectionTitle */ ";
2906 if ( $this->missingComment ) {
2907 $wgOut->wrapWikiMsg(
"<div id='mw-missingcommenttext'>\n$1\n</div>",
'missingcommenttext' );
2910 if ( $this->missingSummary && $this->section !=
'new' ) {
2911 $wgOut->wrapWikiMsg(
"<div id='mw-missingsummary'>\n$1\n</div>",
'missingsummary' );
2914 if ( $this->missingSummary && $this->section ==
'new' ) {
2915 $wgOut->wrapWikiMsg(
"<div id='mw-missingcommentheader'>\n$1\n</div>",
'missingcommentheader' );
2918 if ( $this->blankArticle ) {
2919 $wgOut->wrapWikiMsg(
"<div id='mw-blankarticle'>\n$1\n</div>",
'blankarticle' );
2922 if ( $this->selfRedirect ) {
2923 $wgOut->wrapWikiMsg(
"<div id='mw-selfredirect'>\n$1\n</div>",
'selfredirect' );
2926 if ( $this->hookError !==
'' ) {
2927 $wgOut->addWikiText( $this->hookError );
2931 $wgOut->addWikiMsg(
'nonunicodebrowser' );
2934 if ( $this->section !=
'new' ) {
2935 $revision = $this->mArticle->getRevisionFetched();
2941 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2942 'rev-deleted-text-permission'
2946 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2947 'rev-deleted-text-view'
2951 if ( !$revision->isCurrent() ) {
2952 $this->mArticle->setOldSubtitle( $revision->getId() );
2953 $wgOut->addWikiMsg(
'editingold' );
2955 } elseif ( $this->mTitle->exists() ) {
2958 $wgOut->wrapWikiMsg(
"<div class='errorbox'>\n$1\n</div>\n",
2959 [
'missing-revision', $this->oldid ] );
2966 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
2969 } elseif (
$wgUser->isAnon() ) {
2970 if ( $this->formtype !=
'preview' ) {
2972 "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
2973 [
'anoneditwarning',
2976 'returnto' => $this->
getTitle()->getPrefixedDBkey()
2980 'returnto' => $this->
getTitle()->getPrefixedDBkey()
2985 $wgOut->wrapWikiMsg(
"<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
2986 'anonpreviewwarning'
2990 if ( $this->isCssJsSubpage ) {
2991 # Check the skin exists
2993 $wgOut->wrapWikiMsg(
2994 "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
2995 [
'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
2998 if ( $this->
getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
2999 $wgOut->wrapWikiMsg(
'<div class="mw-usercssjspublic">$1</div>',
3000 $this->isCssSubpage ?
'usercssispublic' :
'userjsispublic'
3002 if ( $this->formtype !==
'preview' ) {
3003 if ( $this->isCssSubpage && $wgAllowUserCss ) {
3005 "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3006 [
'usercssyoucanpreview' ]
3010 if ( $this->isJsSubpage && $wgAllowUserJs ) {
3012 "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3013 [
'userjsyoucanpreview' ]
3021 if ( $this->mTitle->isProtected(
'edit' ) &&
3024 # Is the title semi-protected?
3025 if ( $this->mTitle->isSemiProtected() ) {
3026 $noticeMsg =
'semiprotectedpagewarning';
3028 # Then it must be protected based on static groups (regular)
3029 $noticeMsg =
'protectedpagewarning';
3032 [
'lim' => 1,
'msgKey' => [ $noticeMsg ] ] );
3034 if ( $this->mTitle->isCascadeProtected() ) {
3035 # Is this page under cascading protection from some source pages?
3037 list( $cascadeSources, ) = $this->mTitle->getCascadeProtectionSources();
3038 $notice =
"<div class='mw-cascadeprotectedwarning'>\n$1\n";
3039 $cascadeSourcesCount = count( $cascadeSources );
3040 if ( $cascadeSourcesCount > 0 ) {
3041 # Explain, and list the titles responsible
3042 foreach ( $cascadeSources
as $page ) {
3043 $notice .=
'* [[:' .
$page->getPrefixedText() .
"]]\n";
3046 $notice .=
'</div>';
3047 $wgOut->wrapWikiMsg( $notice, [
'cascadeprotectedwarning', $cascadeSourcesCount ] );
3049 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions(
'create' ) ) {
3052 'showIfEmpty' =>
false,
3053 'msgKey' => [
'titleprotectedwarning' ],
3054 'wrap' =>
"<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
3057 if ( $this->contentLength ===
false ) {
3058 $this->contentLength = strlen( $this->textbox1 );
3062 $wgOut->wrapWikiMsg(
"<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
3065 $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ),
3070 if ( !$this->context->msg(
'longpage-hint' )->isDisabled() ) {
3071 $wgOut->wrapWikiMsg(
"<div id='mw-edit-longpage-hint'>\n$1\n</div>",
3074 $wgLang->formatSize( strlen( $this->textbox1 ) ),
3075 strlen( $this->textbox1 )
3080 # Add header copyright warning
3101 $inputAttrs =
null, $spanLabelAttrs =
null
3104 $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3105 'id' =>
'wpSummary',
3106 'maxlength' =>
'200',
3109 'spellcheck' =>
'true',
3112 $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
3113 'class' => $this->missingSummary ?
'mw-summarymissed' :
'mw-summary',
3114 'id' =>
"wpSummaryLabel"
3121 $inputAttrs[
'id'] ? [
'for' => $inputAttrs[
'id'] ] :
null,
3124 $label =
Xml::tags(
'span', $spanLabelAttrs, $label );
3129 return [ $label, $input ];
3140 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3141 $summaryClass = $this->missingSummary ?
'mw-summarymissed' :
'mw-summary';
3142 if ( $isSubjectPreview ) {
3143 if ( $this->nosummary ) {
3147 if ( !$this->mShowSummaryField ) {
3151 $labelText = $this->context->msg( $isSubjectPreview ?
'subject' :
'summary' )->parse();
3155 [
'class' => $summaryClass ],
3158 $wgOut->addHTML(
"{$label} {$input}" );
3171 if ( !
$summary || ( !$this->preview && !$this->
diff ) ) {
3177 if ( $isSubjectPreview ) {
3178 $summary = $this->context->msg(
'newsectionsummary' )
3180 ->inContentLanguage()->text();
3183 $message = $isSubjectPreview ?
'subject-preview' :
'summary-preview';
3185 $summary = $this->context->msg( $message )->parse()
3192 $section = htmlspecialchars( $this->section );
3194 <input
type=
'hidden' value=
"{$section}" name=
"wpSection"/>
3195 <input
type=
'hidden' value=
"{$this->starttime}" name=
"wpStarttime" />
3196 <input
type=
'hidden' value=
"{$this->edittime}" name=
"wpEdittime" />
3197 <input
type=
'hidden' value=
"{$this->editRevId}" name=
"editRevId" />
3198 <input
type=
'hidden' value=
"{$this->scrolltop}" name=
"wpScrolltop" id=
"wpScrolltop" />
3246 $attribs = [
'style' =>
'display:none;' ];
3249 if ( $this->mTitle->isProtected(
'edit' ) &&
3252 # Is the title semi-protected?
3253 if ( $this->mTitle->isSemiProtected() ) {
3254 $classes[] =
'mw-textarea-sprotected';
3256 # Then it must be protected based on static groups (regular)
3257 $classes[] =
'mw-textarea-protected';
3259 # Is the title cascade-protected?
3260 if ( $this->mTitle->isCascadeProtected() ) {
3261 $classes[] =
'mw-textarea-cprotected';
3271 if ( count( $classes ) ) {
3272 if ( isset(
$attribs[
'class'] ) ) {
3275 $attribs[
'class'] = implode(
' ', $classes );
3280 $textoverride !==
null ? $textoverride : $this->textbox1,
3287 $this->
showTextbox( $this->textbox2,
'wpTextbox2', [
'tabindex' => 6,
'readonly' ] );
3294 if ( strval( $wikitext ) !==
'' ) {
3305 'cols' =>
$wgUser->getIntOption(
'cols' ),
3306 'rows' =>
$wgUser->getIntOption(
'rows' ),
3317 $class =
'mw-editfont-' .
$wgUser->getOption(
'editfont' );
3319 if ( isset(
$attribs[
'class'] ) ) {
3320 if ( is_string(
$attribs[
'class'] ) ) {
3322 } elseif ( is_array(
$attribs[
'class'] ) ) {
3329 $pageLang = $this->mTitle->getPageLanguage();
3330 $attribs[
'lang'] = $pageLang->getHtmlCode();
3331 $attribs[
'dir'] = $pageLang->getDir();
3340 $classes[] =
'ontop';
3343 $attribs = [
'id' =>
'wikiPreview',
'class' => implode(
' ', $classes ) ];
3345 if ( $this->formtype !=
'preview' ) {
3346 $attribs[
'style'] =
'display: none;';
3351 if ( $this->formtype ==
'preview' ) {
3355 $pageViewLang = $this->mTitle->getPageViewLanguage();
3356 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3357 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3361 $wgOut->addHTML(
'</div>' );
3363 if ( $this->formtype ==
'diff' ) {
3367 $msg = $this->context->msg(
3368 'content-failed-to-parse',
3369 $this->contentModel,
3370 $this->contentFormat,
3373 $wgOut->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
3386 if ( $this->mTitle->getNamespace() ==
NS_CATEGORY ) {
3387 $this->mArticle->openShowCategory();
3389 # This hook seems slightly odd here, but makes things more
3390 # consistent for extensions.
3392 $wgOut->addHTML( $text );
3393 if ( $this->mTitle->getNamespace() ==
NS_CATEGORY ) {
3394 $this->mArticle->closeShowCategory();
3408 $oldtitlemsg =
'currentrev';
3409 # if message does not exist, show diff against the preloaded default
3410 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3411 $oldtext = $this->mTitle->getDefaultMessageText();
3412 if ( $oldtext !==
false ) {
3413 $oldtitlemsg =
'defaultmessagetext';
3423 if ( $this->editRevId !==
null ) {
3424 $newContent = $this->
page->replaceSectionAtRev(
3425 $this->section, $textboxContent, $this->summary, $this->editRevId
3428 $newContent = $this->
page->replaceSectionContent(
3429 $this->section, $textboxContent, $this->summary, $this->edittime
3433 if ( $newContent ) {
3435 Hooks::run(
'EditPageGetDiffContent', [ $this, &$newContent ] );
3438 $newContent = $newContent->preSaveTransform( $this->mTitle,
$wgUser, $popts );
3441 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3442 $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3443 $newtitle = $this->context->msg(
'yourtext' )->parse();
3445 if ( !$oldContent ) {
3446 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3449 if ( !$newContent ) {
3450 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3453 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
3454 $de->setContent( $oldContent, $newContent );
3456 $difftext = $de->getDiff( $oldtitle, $newtitle );
3457 $de->showDiffStyle();
3462 $wgOut->addHTML(
'<div id="wikiDiff">' . $difftext .
'</div>' );
3469 $msg =
'editpage-head-copy-warn';
3470 if ( !$this->context->msg( $msg )->isDisabled() ) {
3472 $wgOut->wrapWikiMsg(
"<div class='editpage-head-copywarn'>\n$1\n</div>",
3473 'editpage-head-copy-warn' );
3486 $msg =
'editpage-tos-summary';
3487 Hooks::run(
'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3488 if ( !$this->context->msg( $msg )->isDisabled() ) {
3490 $wgOut->addHTML(
'<div class="mw-tos-summary">' );
3491 $wgOut->addWikiMsg( $msg );
3492 $wgOut->addHTML(
'</div>' );
3498 $wgOut->addHTML(
'<div class="mw-editTools">' .
3499 $this->context->msg(
'edittools' )->inContentLanguage()->parse() .
3522 if ( $wgRightsText ) {
3523 $copywarnMsg = [
'copyrightwarning',
3524 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]',
3527 $copywarnMsg = [
'copyrightwarning2',
3528 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]' ];
3533 $msg = call_user_func_array(
'wfMessage', $copywarnMsg )->title(
$title );
3535 $msg->inLanguage( $langcode );
3537 return "<div id=\"editpage-copywarn\">\n" .
3538 $msg->$format() .
"\n</div>";
3553 $limitReport =
Html::rawElement(
'div', [
'class' =>
'mw-limitReportExplanation' ],
3554 wfMessage(
'limitreport-title' )->parseAsBlock()
3558 $limitReport .=
Html::openElement(
'div', [
'class' =>
'preview-limit-report-wrapper' ] );
3561 'class' =>
'preview-limit-report wikitable'
3567 [ $key, &
$value, &$limitReport,
true,
true ]
3570 $valueMsg =
wfMessage( [
"$key-value-html",
"$key-value" ] );
3571 if ( !$valueMsg->exists() ) {
3572 $valueMsg =
new RawMessage(
'$1' );
3574 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3587 return $limitReport;
3592 $wgOut->addHTML(
"<div class='editOptions'>\n" );
3594 if ( $this->section !=
'new' ) {
3600 [
'minor' => $this->minoredit,
'watch' => $this->watchthis ] );
3601 $wgOut->addHTML(
"<div class='editCheckboxes'>" . implode( $checkboxes,
"\n" ) .
"</div>\n" );
3605 $wgOut->addHTML( $this->editFormTextAfterWarn );
3607 $wgOut->addHTML(
"<div class='editButtons'>\n" );
3611 if ( $cancel !==
'' ) {
3613 [
'class' =>
'mw-editButtons-pipe-separator' ],
3614 $this->context->msg(
'pipe-separator' )->text() );
3617 $message = $this->context->msg(
'edithelppage' )->inContentLanguage()->text();
3620 'target' =>
'helpwindow',
3621 'href' => $edithelpurl,
3624 $attrs, [
'mw-ui-quiet' ] ) .
3625 $this->context->msg(
'word-separator' )->escaped() .
3626 $this->context->msg(
'newwindow' )->parse();
3628 $wgOut->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3629 $wgOut->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3630 $wgOut->addHTML(
"</div><!-- editButtons -->\n" );
3634 $wgOut->addHTML(
"</div><!-- editOptions -->\n" );
3645 $stats =
$wgOut->getContext()->getStats();
3646 $stats->increment(
'edit.failures.conflict' );
3649 $this->mTitle->getNamespace() >=
NS_MAIN &&
3652 $stats->increment(
'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
3655 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
3661 $de =
$handler->createDifferenceEngine( $this->mArticle->getContext() );
3662 $de->setContent( $content2, $content1 );
3664 $this->context->msg(
'yourtext' )->parse(),
3665 $this->context->msg(
'storedversion' )->text()
3668 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
3678 if ( !$this->isConflict && $this->oldid > 0 ) {
3681 $cancelParams[
'redirect'] =
'no';
3683 $attrs = [
'id' =>
'mw-editform-cancel' ];
3687 $this->context->msg(
'cancel' )->parse(),
3703 return $title->getLocalURL( [
'action' => $this->action ] );
3714 if ( $this->deletedSinceEdit !==
null ) {
3718 $this->deletedSinceEdit =
false;
3720 if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3722 if ( $this->lastDelete ) {
3724 if ( $deleteTime > $this->starttime ) {
3725 $this->deletedSinceEdit =
true;
3738 $data =
$dbr->selectRow(
3739 [
'logging',
'user' ],
3752 'log_namespace' => $this->mTitle->getNamespace(),
3753 'log_title' => $this->mTitle->getDBkey(),
3754 'log_type' =>
'delete',
3755 'log_action' =>
'delete',
3759 [
'LIMIT' => 1,
'ORDER BY' =>
'log_timestamp DESC' ]
3762 if ( is_object( $data ) ) {
3764 $data->user_name = $this->context->msg(
'rev-deleted-user' )->escaped();
3768 $data->log_comment = $this->context->msg(
'rev-deleted-comment' )->escaped();
3782 global $wgAllowUserCss, $wgAllowUserJs;
3784 $stats =
$wgOut->getContext()->getStats();
3786 if ( $wgRawHtml && !$this->mTokenOk ) {
3790 if ( $this->textbox1 !==
'' ) {
3794 $parsedNote =
$wgOut->parse(
"<div class='previewnote'>" .
3795 $this->context->msg(
'session_fail_preview_html' )->text() .
"</div>",
3798 $stats->increment(
'edit.failures.session_loss' );
3809 'AlternateEditPreview',
3810 [ $this, &
$content, &$previewHTML, &$this->mParserOutput ] )
3812 return $previewHTML;
3815 # provide a anchor link to the editform
3816 $continueEditing =
'<span class="mw-continue-editing">' .
3817 '[[#' . self::EDITFORM_ID .
'|' .
$wgLang->getArrow() .
' ' .
3818 $this->context->
msg(
'continue-editing' )->text() .
']]</span>';
3819 if ( $this->mTriedSave && !$this->mTokenOk ) {
3820 if ( $this->mTokenOkExceptSuffix ) {
3821 $note = $this->context->msg(
'token_suffix_mismatch' )->plain();
3822 $stats->increment(
'edit.failures.bad_token' );
3824 $note = $this->context->msg(
'session_fail_preview' )->plain();
3825 $stats->increment(
'edit.failures.session_loss' );
3827 } elseif ( $this->incompleteForm ) {
3828 $note = $this->context->msg(
'edit_form_incomplete' )->plain();
3829 if ( $this->mTriedSave ) {
3830 $stats->increment(
'edit.failures.incomplete_form' );
3833 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
3836 # don't parse non-wikitext pages, show message about preview
3837 if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3838 if ( $this->mTitle->isCssJsSubpage() ) {
3840 } elseif ( $this->mTitle->isCssOrJsPage() ) {
3848 if ( $level ===
'user' && !$wgAllowUserCss ) {
3853 if ( $level ===
'user' && !$wgAllowUserJs ) {
3860 # Used messages to make sure grep find them:
3861 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3862 if ( $level && $format ) {
3863 $note =
"<div id='mw-{$level}{$format}preview'>" .
3864 $this->context->msg(
"{$level}{$format}preview" )->text() .
3865 ' ' . $continueEditing .
"</div>";
3869 # If we're adding a comment, we need to show the
3870 # summary as the headline
3871 if ( $this->section ===
"new" && $this->summary !==
"" ) {
3877 Hooks::run(
'EditPageGetPreviewContent', $hook_args );
3881 $previewHTML = $parserResult[
'html'];
3886 $note .=
"\n\n" . implode(
"\n\n",
$parserOutput->getWarnings() );
3890 $m = $this->context->msg(
3891 'content-failed-to-parse',
3892 $this->contentModel,
3893 $this->contentFormat,
3896 $note .=
"\n\n" . $m->parse();
3900 if ( $this->isConflict ) {
3901 $conflict =
'<h2 id="mw-previewconflict">'
3902 . $this->context->msg(
'previewconflict' )->escaped() .
"</h2>\n";
3904 $conflict =
'<hr />';
3907 $previewhead =
"<div class='previewnote'>\n" .
3908 '<h2 id="mw-previewheader">' . $this->context->msg(
'preview' )->escaped() .
"</h2>" .
3909 $wgOut->parse( $note,
true,
true ) . $conflict .
"</div>\n";
3911 $pageViewLang = $this->mTitle->getPageViewLanguage();
3912 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3913 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3924 $parserOptions = $this->
page->makeParserOptions( $this->mArticle->getContext() );
3925 $parserOptions->setIsPreview(
true );
3926 $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !==
'' );
3927 $parserOptions->enableLimitReport();
3928 return $parserOptions;
3943 $pstContent =
$content->preSaveTransform( $this->mTitle,
$wgUser, $parserOptions );
3944 $scopedCallback = $parserOptions->setupFakeRevision(
3945 $this->mTitle, $pstContent,
$wgUser );
3946 $parserOutput = $pstContent->getParserOutput( $this->mTitle,
null, $parserOptions );
3947 ScopedCallback::consume( $scopedCallback );
3958 if ( $this->preview || $this->section !=
'' ) {
3960 if ( !isset( $this->mParserOutput ) ) {
3963 foreach ( $this->mParserOutput->getTemplates()
as $ns =>
$template ) {
3970 return $this->mTitle->getTemplateLinksFrom();
3986 $showSignature =
true;
4002 'id' =>
'mw-editbutton-bold',
4004 'close' =>
'\'\
'\'',
4005 'sample' =>
wfMessage(
'bold_sample' )->text(),
4006 'tip' =>
wfMessage(
'bold_tip' )->text(),
4009 'id' =>
'mw-editbutton-italic',
4012 'sample' =>
wfMessage(
'italic_sample' )->text(),
4013 'tip' =>
wfMessage(
'italic_tip' )->text(),
4016 'id' =>
'mw-editbutton-link',
4019 'sample' =>
wfMessage(
'link_sample' )->text(),
4020 'tip' =>
wfMessage(
'link_tip' )->text(),
4023 'id' =>
'mw-editbutton-extlink',
4026 'sample' =>
wfMessage(
'extlink_sample' )->text(),
4027 'tip' =>
wfMessage(
'extlink_tip' )->text(),
4030 'id' =>
'mw-editbutton-headline',
4033 'sample' =>
wfMessage(
'headline_sample' )->text(),
4034 'tip' =>
wfMessage(
'headline_tip' )->text(),
4036 $imagesAvailable ? [
4037 'id' =>
'mw-editbutton-image',
4040 'sample' =>
wfMessage(
'image_sample' )->text(),
4041 'tip' =>
wfMessage(
'image_tip' )->text(),
4043 $imagesAvailable ? [
4044 'id' =>
'mw-editbutton-media',
4047 'sample' =>
wfMessage(
'media_sample' )->text(),
4048 'tip' =>
wfMessage(
'media_tip' )->text(),
4051 'id' =>
'mw-editbutton-nowiki',
4052 'open' =>
"<nowiki>",
4053 'close' =>
"</nowiki>",
4054 'sample' =>
wfMessage(
'nowiki_sample' )->text(),
4055 'tip' =>
wfMessage(
'nowiki_tip' )->text(),
4058 'id' =>
'mw-editbutton-signature',
4059 'open' =>
wfMessage(
'sig-text',
'~~~~' )->inContentLanguage()->text(),
4062 'tip' =>
wfMessage(
'sig_tip' )->text(),
4065 'id' =>
'mw-editbutton-hr',
4066 'open' =>
"\n----\n",
4073 $script =
'mw.loader.using("mediawiki.toolbar", function () {';
4074 foreach ( $toolarray
as $tool ) {
4094 'mw.toolbar.addButton',
4096 ResourceLoader::inDebugMode()
4101 $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
4103 $toolbar =
'<div id="toolbar"></div>';
4105 Hooks::run(
'EditPageBeforeEditToolbar', [ &$toolbar ] );
4126 if ( !$this->isNew ) {
4127 $checkboxes[
'minor'] =
'';
4128 $minorLabel = $this->context->msg(
'minoredit' )->parse();
4129 if (
$wgUser->isAllowed(
'minoredit' ) ) {
4132 'accesskey' => $this->context->msg(
'accesskey-minoredit' )->text(),
4133 'id' =>
'wpMinoredit',
4137 " <label for='wpMinoredit' id='mw-editpage-minoredit'" .
4139 ">{$minorLabel}</label>";
4141 if ( $wgUseMediaWikiUIEverywhere ) {
4142 $checkboxes[
'minor'] =
Html::openElement(
'div', [
'class' =>
'mw-ui-checkbox' ] ) .
4146 $checkboxes[
'minor'] = $minorEditHtml;
4151 $watchLabel = $this->context->msg(
'watchthis' )->parse();
4152 $checkboxes[
'watch'] =
'';
4153 if (
$wgUser->isLoggedIn() ) {
4156 'accesskey' => $this->context->msg(
'accesskey-watch' )->text(),
4157 'id' =>
'wpWatchthis',
4161 " <label for='wpWatchthis' id='mw-editpage-watch'" .
4163 ">{$watchLabel}</label>";
4164 if ( $wgUseMediaWikiUIEverywhere ) {
4165 $checkboxes[
'watch'] =
Html::openElement(
'div', [
'class' =>
'mw-ui-checkbox' ] ) .
4169 $checkboxes[
'watch'] = $watchThisHtml;
4188 $this->mArticle->getContext()->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4191 if ( $labelAsPublish ) {
4192 $buttonLabelKey = !$this->mTitle->exists() ?
'publishpage' :
'publishchanges';
4194 $buttonLabelKey = !$this->mTitle->exists() ?
'savearticle' :
'savechanges';
4196 $buttonLabel = $this->context->msg( $buttonLabelKey )->text();
4206 'id' =>
'wpPreview',
4207 'name' =>
'wpPreview',
4210 $buttons[
'preview'] =
Html::submitButton( $this->context->msg(
'showpreview' )->text(),
4212 $buttons[
'live'] =
'';
4233 $wgOut->prepareErrorPage( $this->context->msg(
'nosuchsectiontitle' ) );
4235 $res = $this->context->msg(
'nosuchsectiontext', $this->section )->parseAsBlock();
4239 $wgOut->returnToMain(
false, $this->mTitle );
4251 if ( is_array( $match ) ) {
4252 $match =
$wgLang->listToText( $match );
4254 $wgOut->prepareErrorPage( $this->context->msg(
'spamprotectiontitle' ) );
4256 $wgOut->addHTML(
'<div id="spamprotected">' );
4257 $wgOut->addWikiMsg(
'spamprotectiontext' );
4261 $wgOut->addHTML(
'</div>' );
4263 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
4266 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
4269 $wgOut->addReturnTo( $this->
getContextTitle(), [
'action' =>
'edit' ] );
4281 $currentbrowser =
$wgRequest->getHeader(
'User-Agent' );
4282 if ( $currentbrowser ===
false ) {
4287 foreach ( $wgBrowserBlackList
as $browser ) {
4288 if ( preg_match( $browser, $currentbrowser ) ) {
4304 $text = rtrim(
$request->getText( $field ) );
4305 return $request->getBool(
'safemode' )
4337 $invalue = strtr( $invalue, [
"&#x" =>
"�" ] );
4342 $valueLength = strlen( $invalue );
4343 for ( $i = 0; $i < $valueLength; $i++ ) {
4344 $bytevalue = ord( $invalue[$i] );
4345 if ( $bytevalue <= 0x7F ) {
4348 } elseif ( $bytevalue <= 0xBF ) {
4349 $working = $working << 6;
4350 $working += ( $bytevalue & 0x3F );
4352 if ( $bytesleft <= 0 ) {
4353 $result .=
"&#x" . strtoupper( dechex( $working ) ) .
";";
4355 } elseif ( $bytevalue <= 0xDF ) {
4356 $working = $bytevalue & 0x1F;
4358 } elseif ( $bytevalue <= 0xEF ) {
4359 $working = $bytevalue & 0x0F;
4362 $working = $bytevalue & 0x07;
4379 $valueLength = strlen( $invalue );
4380 for ( $i = 0; $i < $valueLength; $i++ ) {
4381 if ( ( substr( $invalue, $i, 3 ) ==
"&#x" ) && ( $invalue[$i + 3] !=
'0' ) ) {
4385 $hexstring .= $invalue[$i];
4387 }
while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
4392 if ( ( substr( $invalue, $i, 1 ) ==
";" ) && ( strlen( $hexstring ) <= 6 ) ) {
4393 $codepoint = hexdec( $hexstring );
4396 $result .=
"&#x" . $hexstring . substr( $invalue, $i, 1 );
4399 $result .= substr( $invalue, $i, 1 );
4403 return strtr(
$result, [
"�" =>
"&#x" ] );