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