25use Wikimedia\ScopedCallback;
403 # Placeholders for text injection by hooks (must be HTML)
404 # extensions should take care to _append_ to the present value
454 $this->page = $article->getPage();
455 $this->mTitle = $article->getTitle();
456 $this->context = $article->getContext();
458 $this->contentModel = $this->mTitle->getContentModel();
461 $this->contentFormat =
$handler->getDefaultFormat();
493 $this->mContextTitle =
$title;
504 if ( is_null( $this->mContextTitle ) ) {
507 __METHOD__ .
' called by ' .
wfGetAllCallers( 5 ) .
' with no title set.'
533 return $this->enableApiEditOverride ===
true ||
544 $this->enableApiEditOverride = $enableOverride;
568 if ( !Hooks::run(
'AlternateEdit', [ $this ] ) ) {
572 wfDebug( __METHOD__ .
": enter\n" );
574 $request = $this->context->getRequest();
576 if (
$request->getBool(
'redlink' ) && $this->mTitle->exists() ) {
577 $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
582 $this->firsttime =
false;
587 $this->preview =
true;
591 $this->formtype =
'save';
592 } elseif ( $this->preview ) {
593 $this->formtype =
'preview';
594 } elseif ( $this->
diff ) {
595 $this->formtype =
'diff';
596 }
else { #
First time through
597 $this->firsttime =
true;
599 $this->formtype =
'preview';
601 $this->formtype =
'initial';
607 wfDebug( __METHOD__ .
": User can't edit\n" );
610 DeferredUpdates::addCallableUpdate(
function () {
611 $this->context->getUser()->spreadAnyEditBlock();
619 $revision = $this->mArticle->getRevisionFetched();
623 && $revision->getContentModel() !== $this->contentModel
626 if ( $this->undidRev ) {
628 $prevRev = $undidRevObj ? $undidRevObj->getPrevious() :
null;
630 if ( !$this->undidRev
632 || $prevRev->getContentModel() !== $this->contentModel
637 'contentmodelediterror',
638 $revision->getContentModel(),
646 $this->isConflict =
false;
650 $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
651 $this->isCssSubpage = $this->mTitle->isCssSubpage();
652 $this->isJsSubpage = $this->mTitle->isJsSubpage();
655 # Show applicable editing introductions
656 if ( $this->formtype ==
'initial' || $this->firsttime ) {
660 # Attempt submission here. This will check for edit conflicts,
661 # and redundantly check for locked database, blocked IPs, etc.
662 # that edit() already checked just in case someone tries to sneak
663 # in the back door with a hand-edited submission URL.
665 if (
'save' == $this->formtype ) {
666 $resultDetails =
null;
673 # First time through: get contents, set time for conflict
675 if (
'initial' == $this->formtype || $this->firsttime ) {
681 if ( !$this->mTitle->getArticleID() ) {
682 Hooks::run(
'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
684 Hooks::run(
'EditFormInitialText', [ $this ] );
697 $user = $this->context->getUser();
698 $permErrors = $this->mTitle->getUserPermissionsErrors(
'edit', $user, $rigor );
699 # Can this title be created?
700 if ( !$this->mTitle->exists() ) {
701 $permErrors = array_merge(
704 $this->mTitle->getUserPermissionsErrors(
'create', $user, $rigor ),
709 # Ignore some permissions errors when a user is just previewing/viewing diffs
711 foreach ( $permErrors as $error ) {
712 if ( ( $this->preview || $this->
diff )
714 $error[0] ==
'blockedtext' ||
715 $error[0] ==
'autoblockedtext' ||
716 $error[0] ==
'systemblockedtext'
741 $out = $this->context->getOutput();
742 if ( $this->context->getRequest()->getBool(
'redlink' ) ) {
746 $out->redirect( $this->mTitle->getFullURL() );
752 # Use the normal message if there's nothing to display
753 if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
754 $action = $this->mTitle->exists() ?
'edit' :
755 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
761 $out->formatPermissionsErrorMessage( $permErrors,
'edit' )
771 $out = $this->context->getOutput();
772 Hooks::run(
'EditPage::showReadOnlyForm:initial', [ $this, &
$out ] );
774 $out->setRobotPolicy(
'noindex,nofollow' );
775 $out->setPageTitle( $this->context->msg(
777 $this->getContextTitle()->getPrefixedText()
780 $out->addHTML( $this->editFormPageTop );
781 $out->addHTML( $this->editFormTextTop );
783 if ( $errorMessage !==
'' ) {
784 $out->addWikiText( $errorMessage );
785 $out->addHTML(
"<hr />\n" );
788 # If the user made changes, preserve them when showing the markup
789 # (This happens when a user is blocked during edit, for instance)
790 if ( !$this->firsttime ) {
792 $out->addWikiMsg(
'viewyourtext' );
797 # Serialize using the default format if the content model is not supported
798 # (e.g. for an old revision with a different model)
801 $out->addWikiMsg(
'viewsourcetext' );
804 $out->addHTML( $this->editFormTextBeforeContent );
805 $this->
showTextbox( $text,
'wpTextbox1', [
'readonly' ] );
806 $out->addHTML( $this->editFormTextAfterContent );
810 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
812 $out->addHTML( $this->editFormTextBottom );
813 if ( $this->mTitle->exists() ) {
814 $out->returnToMain(
null, $this->mTitle );
824 $previewOnOpenNamespaces = $this->context->getConfig()->get(
'PreviewOnOpenNamespaces' );
825 $request = $this->context->getRequest();
826 if (
$request->getVal(
'preview' ) ==
'yes' ) {
829 } elseif (
$request->getVal(
'preview' ) ==
'no' ) {
832 } elseif ( $this->section ==
'new' ) {
835 } elseif ( (
$request->getVal(
'preload' ) !==
null || $this->mTitle->exists() )
836 && $this->context->getUser()->getOption(
'previewonfirst' )
840 } elseif ( !$this->mTitle->exists()
841 && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
842 && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
858 if ( $this->mTitle->isCssJsSubpage() ) {
859 $name = $this->mTitle->getSkinFromCssJsSubpage();
860 $skins = array_merge(
864 return !in_array( $name, $skins )
865 && in_array( strtolower( $name ), $skins );
880 return $contentHandler->supportsSections();
889 # Section edit can come from either the form or a link
890 $this->section =
$request->getVal(
'wpSection',
$request->getVal(
'section' ) );
893 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
896 $this->isNew = !$this->mTitle->exists() || $this->section ==
'new';
899 # These fields need to be checked for encoding.
900 # Also remove trailing whitespace, but don't remove _initial_
901 # whitespace from the text boxes. This may be significant formatting.
902 $this->textbox1 = rtrim(
$request->getText(
'wpTextbox1' ) );
903 if ( !
$request->getCheck(
'wpTextbox2' ) ) {
913 $this->unicodeCheck =
$request->getText(
'wpUnicodeCheck' );
915 $this->summary =
$request->getText(
'wpSummary' );
917 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
918 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
920 $this->summary = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->summary );
922 # Treat sectiontitle the same way as summary.
923 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
924 # currently doing double duty as both edit summary and section title. Right now this
925 # is just to allow API edits to work around this limitation, but this should be
926 # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
927 $this->sectiontitle =
$request->getText(
'wpSectionTitle' );
928 $this->sectiontitle = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->sectiontitle );
930 $this->edittime =
$request->getVal(
'wpEdittime' );
931 $this->editRevId =
$request->getIntOrNull(
'editRevId' );
932 $this->starttime =
$request->getVal(
'wpStarttime' );
939 $this->scrolltop =
$request->getIntOrNull(
'wpScrolltop' );
941 if ( $this->textbox1 ===
'' &&
$request->getVal(
'wpTextbox1' ) ===
null ) {
945 $this->incompleteForm =
true;
953 $this->incompleteForm = ( !
$request->getVal(
'wpUltimateParam' )
954 && is_null( $this->edittime ) );
956 if ( $this->incompleteForm ) {
957 # If the form is incomplete, force to preview.
958 wfDebug( __METHOD__ .
": Form data appears to be incomplete\n" );
959 wfDebug(
"POST DATA: " . var_export( $_POST,
true ) .
"\n" );
960 $this->preview =
true;
962 $this->preview =
$request->getCheck(
'wpPreview' );
970 # Some browsers will not report any submit button
971 # if the user hits enter in the comment box.
972 # The unmarked state will be assumed to be a save,
973 # if the form seems otherwise complete.
974 wfDebug( __METHOD__ .
": Passed token check.\n" );
975 } elseif ( $this->
diff ) {
976 # Failed token check, but only requested "Show Changes".
977 wfDebug( __METHOD__ .
": Failed token check; Show Changes requested.\n" );
979 # Page might be a hack attempt posted from
980 # an external site. Preview instead of saving.
981 wfDebug( __METHOD__ .
": Failed token check; forcing preview\n" );
982 $this->preview =
true;
986 if ( !preg_match(
'/^\d{14}$/', $this->edittime ) ) {
987 $this->edittime =
null;
990 if ( !preg_match(
'/^\d{14}$/', $this->starttime ) ) {
991 $this->starttime =
null;
994 $this->recreate =
$request->getCheck(
'wpRecreate' );
996 $this->minoredit =
$request->getCheck(
'wpMinoredit' );
997 $this->watchthis =
$request->getCheck(
'wpWatchthis' );
999 $user = $this->context->getUser();
1000 # Don't force edit summaries when a user is editing their own user or talk page
1001 if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace ==
NS_USER_TALK )
1002 && $this->mTitle->getText() == $user->getName()
1004 $this->allowBlankSummary =
true;
1006 $this->allowBlankSummary =
$request->getBool(
'wpIgnoreBlankSummary' )
1007 || !$user->getOption(
'forceeditsummary' );
1010 $this->autoSumm =
$request->getText(
'wpAutoSummary' );
1012 $this->allowBlankArticle =
$request->getBool(
'wpIgnoreBlankArticle' );
1013 $this->allowSelfRedirect =
$request->getBool(
'wpIgnoreSelfRedirect' );
1017 $this->changeTags = [];
1019 $this->changeTags = array_filter( array_map(
'trim', explode(
',',
1023 # Not a posted form? Start with nothing.
1024 wfDebug( __METHOD__ .
": Not a posted form.\n" );
1025 $this->textbox1 =
'';
1026 $this->summary =
'';
1027 $this->sectiontitle =
'';
1028 $this->edittime =
'';
1029 $this->editRevId =
null;
1031 $this->edit =
false;
1032 $this->preview =
false;
1033 $this->
save =
false;
1034 $this->
diff =
false;
1035 $this->minoredit =
false;
1037 $this->watchthis =
$request->getBool(
'watchthis',
false );
1038 $this->recreate =
false;
1042 if ( $this->section ==
'new' &&
$request->getVal(
'preloadtitle' ) ) {
1043 $this->sectiontitle =
$request->getVal(
'preloadtitle' );
1045 $this->summary =
$request->getVal(
'preloadtitle' );
1046 } elseif ( $this->section !=
'new' &&
$request->getVal(
'summary' ) ) {
1047 $this->summary =
$request->getText(
'summary' );
1048 if ( $this->summary !==
'' ) {
1049 $this->hasPresetSummary =
true;
1053 if (
$request->getVal(
'minor' ) ) {
1054 $this->minoredit =
true;
1058 $this->oldid =
$request->getInt(
'oldid' );
1059 $this->parentRevId =
$request->getInt(
'parentRevId' );
1061 $this->bot =
$request->getBool(
'bot',
true );
1062 $this->nosummary =
$request->getBool(
'nosummary' );
1065 $this->contentModel =
$request->getText(
'model', $this->contentModel );
1067 $this->contentFormat =
$request->getText(
'format', $this->contentFormat );
1073 'editpage-invalidcontentmodel-title',
1074 'editpage-invalidcontentmodel-text',
1079 if ( !
$handler->isSupportedFormat( $this->contentFormat ) ) {
1081 'editpage-notsupportedcontentformat-title',
1082 'editpage-notsupportedcontentformat-text',
1096 $this->editintro =
$request->getText(
'editintro',
1098 $this->section ===
'new' ?
'MediaWiki:addsection-editintro' :
'' );
1101 Hooks::run(
'EditPage::importFormData', [ $this,
$request ] );
1123 $this->edittime = $this->page->getTimestamp();
1124 $this->editRevId = $this->page->getLatest();
1126 $content = $this->
getContentObject(
false ); # TODO: track content
object?!
1127 if ( $content ===
false ) {
1130 $this->textbox1 = $this->
toEditText( $content );
1132 $user = $this->context->getUser();
1134 # Sort out the "watch" checkbox
1135 if ( $user->getOption(
'watchdefault' ) ) {
1137 $this->watchthis =
true;
1138 } elseif ( $user->getOption(
'watchcreations' ) && !$this->mTitle->exists() ) {
1140 $this->watchthis =
true;
1141 } elseif ( $user->isWatched( $this->mTitle ) ) {
1143 $this->watchthis =
true;
1145 if ( $user->getOption(
'minordefault' ) && !$this->isNew ) {
1146 $this->minoredit =
true;
1148 if ( $this->textbox1 ===
false ) {
1166 $user = $this->context->getUser();
1167 $request = $this->context->getRequest();
1170 if ( !$this->mTitle->exists() || $this->section ==
'new' ) {
1171 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section !=
'new' ) {
1172 # If this is a system message, get the default text.
1173 $msg = $this->mTitle->getDefaultMessageText();
1177 if ( $content ===
false ) {
1178 # If requested, preload some text.
1179 $preload =
$request->getVal(
'preload',
1181 $this->section ===
'new' ?
'MediaWiki:addsection-preload' :
'' );
1188 if ( $this->section !=
'' ) {
1191 $content = $orig ? $orig->getSection( $this->section ) :
null;
1194 $content = $def_content;
1197 $undoafter =
$request->getInt(
'undoafter' );
1198 $undo =
$request->getInt(
'undo' );
1200 if ( $undo > 0 && $undoafter > 0 ) {
1204 # Sanity check, make sure it's the right page,
1205 # the revisions exist and they were not deleted.
1206 # Otherwise, $content will be left as-is.
1207 if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1211 $content = $this->page->getUndoContent( $undorev, $oldrev );
1213 if ( $content ===
false ) {
1214 # Warn the user that something went wrong
1215 $undoMsg =
'failure';
1218 $popts = ParserOptions::newFromUserAndLang( $user,
$wgContLang );
1219 $newContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
1220 if ( $newContent->getModel() !== $oldContent->getModel() ) {
1225 $this->contentModel = $newContent->getModel();
1226 $this->contentFormat = $oldrev->getContentFormat();
1229 if ( $newContent->equals( $oldContent ) ) {
1230 # Tell the user that the undo results in no change,
1231 # i.e. the revisions were already undone.
1232 $undoMsg =
'nochange';
1235 # Inform the user of our success and set an automatic edit summary
1236 $undoMsg =
'success';
1238 # If we just undid one rev, use an autosummary
1239 $firstrev = $oldrev->getNext();
1240 if ( $firstrev && $firstrev->getId() == $undo ) {
1241 $userText = $undorev->getUserText();
1242 if ( $userText ===
'' ) {
1243 $undoSummary = $this->context->msg(
1244 'undo-summary-username-hidden',
1246 )->inContentLanguage()->text();
1248 $undoSummary = $this->context->msg(
1252 )->inContentLanguage()->text();
1254 if ( $this->summary ===
'' ) {
1255 $this->summary = $undoSummary;
1257 $this->summary = $undoSummary . $this->context->msg(
'colon-separator' )
1260 $this->undidRev = $undo;
1262 $this->formtype =
'diff';
1272 $out = $this->context->getOutput();
1274 $class = ( $undoMsg ==
'success' ?
'' :
'error ' ) .
"mw-undo-{$undoMsg}";
1275 $this->editFormPageTop .=
$out->parse(
"<div class=\"{$class}\">" .
1276 $this->context->msg(
'undo-' . $undoMsg )->plain() .
'</div>',
true,
true );
1279 if ( $content ===
false ) {
1304 if ( $this->section ==
'new' ) {
1307 $revision = $this->mArticle->getRevisionFetched();
1308 if ( $revision ===
null ) {
1310 return $handler->makeEmptyContent();
1329 if ( $this->parentRevId ) {
1332 return $this->mArticle->getRevIdFetched();
1345 $rev = $this->page->getRevision();
1348 if ( $content ===
false || $content ===
null ) {
1350 return $handler->makeEmptyContent();
1351 } elseif ( !$this->undidRev ) {
1356 $logger = LoggerFactory::getInstance(
'editpage' );
1357 if ( $this->contentModel !==
$rev->getContentModel() ) {
1358 $logger->warning(
"Overriding content model from current edit {prev} to {new}", [
1359 'prev' => $this->contentModel,
1360 'new' =>
$rev->getContentModel(),
1361 'title' => $this->getTitle()->getPrefixedDBkey(),
1362 'method' => __METHOD__
1364 $this->contentModel =
$rev->getContentModel();
1369 if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1370 $logger->warning(
"Current revision content format unsupported. Overriding {prev} to {new}", [
1372 'prev' => $this->contentFormat,
1373 'new' =>
$rev->getContentFormat(),
1374 'title' => $this->getTitle()->getPrefixedDBkey(),
1375 'method' => __METHOD__
1377 $this->contentFormat =
$rev->getContentFormat();
1391 $this->mPreloadContent = $content;
1406 if ( !empty( $this->mPreloadContent ) ) {
1412 if ( $preload ===
'' ) {
1413 return $handler->makeEmptyContent();
1416 $user = $this->context->getUser();
1417 $title = Title::newFromText( $preload );
1418 # Check for existence to avoid getting MediaWiki:Noarticletext
1419 if ( $title ===
null || !$title->exists() || !$title->userCan(
'read', $user ) ) {
1421 return $handler->makeEmptyContent();
1428 if ( $title ===
null || !$title->exists() || !$title->userCan(
'read', $user ) ) {
1430 return $handler->makeEmptyContent();
1435 $parserOptions = ParserOptions::newFromUser( $user );
1440 return $handler->makeEmptyContent();
1443 if ( $content->getModel() !==
$handler->getModelID() ) {
1444 $converted = $content->convert(
$handler->getModelID() );
1446 if ( !$converted ) {
1448 wfDebug(
"Attempt to preload incompatible content: " .
1449 "can't convert " . $content->getModel() .
1452 return $handler->makeEmptyContent();
1455 $content = $converted;
1458 return $content->preloadTransform( $title, $parserOptions,
$params );
1469 $token =
$request->getVal(
'wpEditToken' );
1470 $user = $this->context->getUser();
1471 $this->mTokenOk = $user->matchEditToken( $token );
1472 $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1491 $revisionId = $this->page->getLatest();
1492 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1495 if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1497 } elseif ( $this->oldid ) {
1501 $response = $this->context->getRequest()->response();
1502 $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1512 # Allow bots to exempt some edits from bot flagging
1513 $bot = $this->context->getUser()->isAllowed(
'bot' ) &&
$this->bot;
1516 Hooks::run(
'EditPage::attemptSave:after', [ $this,
$status, $resultDetails ] );
1525 if ( $this->context->getRequest()->getText(
'mode' ) !==
'conflict' ) {
1529 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1530 $stats->increment(
'edit.failures.conflict.resolved' );
1547 if (
$status->value == self::AS_SUCCESS_UPDATE
1548 ||
$status->value == self::AS_SUCCESS_NEW_ARTICLE
1552 $this->didSave =
true;
1553 if ( !$resultDetails[
'nullEdit'] ) {
1558 $out = $this->context->getOutput();
1562 $request = $this->context->getRequest();
1563 $extraQueryRedirect =
$request->getVal(
'wpExtraQueryRedirect' );
1584 $out->addWikiText(
'<div class="error">' .
"\n" .
$status->getWikiText() .
'</div>' );
1588 $query = $resultDetails[
'redirect'] ?
'redirect=no' :
'';
1589 if ( $extraQueryRedirect ) {
1591 $query = $extraQueryRedirect;
1596 $anchor = isset( $resultDetails[
'sectionanchor'] ) ? $resultDetails[
'sectionanchor'] :
'';
1597 $out->redirect( $this->mTitle->getFullURL(
$query ) . $anchor );
1602 $sectionanchor = $resultDetails[
'sectionanchor'];
1606 'ArticleUpdateBeforeRedirect',
1607 [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1610 if ( $resultDetails[
'redirect'] ) {
1611 if ( $extraQuery ==
'' ) {
1612 $extraQuery =
'redirect=no';
1614 $extraQuery =
'redirect=no&' . $extraQuery;
1617 if ( $extraQueryRedirect ) {
1618 if ( $extraQuery ===
'' ) {
1619 $extraQuery = $extraQueryRedirect;
1621 $extraQuery = $extraQuery .
'&' . $extraQueryRedirect;
1625 $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1650 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
1661 $this->hookError =
'<div class="error">' .
"\n" .
$status->getWikiText() .
1678 if ( $this->hookError !=
'' ) {
1679 # ...or the hook could be expecting us to produce an error
1680 $status->fatal(
'hookaborted' );
1686 if ( !Hooks::run(
'EditFilterMergedContent',
1687 [ $this->context, $content,
$status, $this->summary,
1688 $user, $this->minoredit ] )
1690 # Error messages etc. could be handled within the hook...
1692 $status->fatal(
'hookaborted' );
1698 $this->hookError =
$status->getWikiText();
1705 } elseif ( !
$status->isOK() ) {
1706 # ...or the hook could be expecting us to produce an error
1708 $this->hookError =
$status->getWikiText();
1709 $status->fatal(
'hookaborted' );
1726 if ( $this->sectiontitle !==
'' ) {
1731 if ( $this->summary ===
'' ) {
1732 $cleanSectionTitle =
$wgParser->stripSectionName( $this->sectiontitle );
1733 return $this->context->msg(
'newsectionsummary' )
1734 ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1736 } elseif ( $this->summary !==
'' ) {
1738 # This is a new section, so create a link to the new section
1739 # in the revision summary.
1740 $cleanSummary =
$wgParser->stripSectionName( $this->summary );
1741 return $this->context->msg(
'newsectionsummary' )
1742 ->rawParams( $cleanSummary )->inContentLanguage()->text();
1773 $user = $this->context->getUser();
1775 if ( !Hooks::run(
'EditPage::attemptSave', [ $this ] ) ) {
1776 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving\n" );
1777 $status->fatal(
'hookaborted' );
1782 if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1783 $status->fatal(
'unicode-support-fail' );
1788 $request = $this->context->getRequest();
1789 $spam =
$request->getText(
'wpAntispam' );
1790 if ( $spam !==
'' ) {
1795 $this->mTitle->getPrefixedText() .
1796 '" submitted bogus field "' .
1800 $status->fatal(
'spamprotectionmatch',
false );
1806 # Construct Content object
1810 'content-failed-to-parse',
1811 $this->contentModel,
1812 $this->contentFormat,
1819 # Check image redirect
1820 if ( $this->mTitle->getNamespace() ==
NS_FILE &&
1821 $textbox_content->isRedirect() &&
1822 !$user->isAllowed(
'upload' )
1832 if ( $match ===
false && $this->section ==
'new' ) {
1833 # $wgSpamRegex is enforced on this new heading/summary because, unlike
1834 # regular summaries, it is added to the actual wikitext.
1835 if ( $this->sectiontitle !==
'' ) {
1836 # This branch is taken when the API is used with the 'sectiontitle' parameter.
1839 # This branch is taken when the "Add Topic" user interface is used, or the API
1840 # is used with the 'summary' parameter.
1844 if ( $match ===
false ) {
1847 if ( $match !==
false ) {
1848 $result[
'spam'] = $match;
1850 $pdbk = $this->mTitle->getPrefixedDBkey();
1851 $match = str_replace(
"\n",
'', $match );
1852 wfDebugLog(
'SpamRegex',
"$ip spam regex hit [[$pdbk]]: \"$match\"" );
1853 $status->fatal(
'spamprotectionmatch', $match );
1859 [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1861 # Error messages etc. could be handled within the hook...
1862 $status->fatal(
'hookaborted' );
1865 } elseif ( $this->hookError !=
'' ) {
1866 # ...or the hook could be expecting us to produce an error
1867 $status->fatal(
'hookaborted' );
1872 if ( $user->isBlockedFrom( $this->mTitle,
false ) ) {
1875 $user->spreadAnyEditBlock();
1877 # Check block state against master, thus 'false'.
1878 $status->setResult(
false, self::AS_BLOCKED_PAGE_FOR_USER );
1882 $this->contentLength = strlen( $this->textbox1 );
1883 $config = $this->context->getConfig();
1884 $maxArticleSize = $config->get(
'MaxArticleSize' );
1885 if ( $this->contentLength > $maxArticleSize * 1024 ) {
1887 $this->tooBig =
true;
1888 $status->setResult(
false, self::AS_CONTENT_TOO_BIG );
1892 if ( !$user->isAllowed(
'edit' ) ) {
1893 if ( $user->isAnon() ) {
1894 $status->setResult(
false, self::AS_READ_ONLY_PAGE_ANON );
1897 $status->fatal(
'readonlytext' );
1903 $changingContentModel =
false;
1904 if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1905 if ( !$config->get(
'ContentHandlerUseDB' ) ) {
1906 $status->fatal(
'editpage-cannot-use-custom-model' );
1909 } elseif ( !$user->isAllowed(
'editcontentmodel' ) ) {
1910 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1916 if ( !$titleWithNewContentModel->userCan(
'editcontentmodel', $user )
1917 || !$titleWithNewContentModel->userCan(
'edit', $user )
1919 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1923 $changingContentModel =
true;
1924 $oldContentModel = $this->mTitle->getContentModel();
1927 if ( $this->changeTags ) {
1929 $this->changeTags, $user );
1930 if ( !$changeTagsStatus->isOK() ) {
1932 return $changeTagsStatus;
1937 $status->fatal(
'readonlytext' );
1941 if ( $user->pingLimiter() || $user->pingLimiter(
'linkpurge', 0 )
1942 || ( $changingContentModel && $user->pingLimiter(
'editcontentmodel' ) )
1944 $status->fatal(
'actionthrottledtext' );
1949 # If the article has been deleted while editing, don't save it without
1952 $status->setResult(
false, self::AS_ARTICLE_WAS_DELETED );
1956 # Load the page data from the master. If anything changes in the meantime,
1957 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1958 $this->page->loadPageData(
'fromdbmaster' );
1959 $new = !$this->page->exists();
1963 if ( !$this->mTitle->userCan(
'create', $user ) ) {
1964 $status->fatal(
'nocreatetext' );
1966 wfDebug( __METHOD__ .
": no create permission\n" );
1973 $defaultMessageText = $this->mTitle->getDefaultMessageText();
1974 if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !==
false ) {
1975 $defaultText = $defaultMessageText;
1980 if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1981 $this->blankArticle =
true;
1982 $status->fatal(
'blankarticle' );
1983 $status->setResult(
false, self::AS_BLANK_ARTICLE );
1991 $content = $textbox_content;
1993 $result[
'sectionanchor'] =
'';
1994 if ( $this->section ==
'new' ) {
1995 if ( $this->sectiontitle !==
'' ) {
1997 $content = $content->addSectionHeader( $this->sectiontitle );
1998 } elseif ( $this->summary !==
'' ) {
2000 $content = $content->addSectionHeader( $this->summary );
2009 # Article exists. Check for edit conflict.
2011 $this->page->clear(); # Force reload of dates, etc.
2012 $timestamp = $this->page->getTimestamp();
2013 $latest = $this->page->getLatest();
2015 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2018 if ( $timestamp != $this->edittime
2019 || ( $this->editRevId !==
null && $this->editRevId != $latest )
2021 $this->isConflict =
true;
2022 if ( $this->section ==
'new' ) {
2023 if ( $this->page->getUserText() == $user->getName() &&
2024 $this->page->getComment() == $this->newSectionSummary()
2030 .
": duplicate new section submission; trigger edit conflict!\n" );
2033 $this->isConflict =
false;
2034 wfDebug( __METHOD__ .
": conflict suppressed; new section\n" );
2036 } elseif ( $this->section ==
''
2038 DB_MASTER, $this->mTitle->getArticleID(),
2039 $user->getId(), $this->edittime
2042 # Suppress edit conflict with self, except for section edits where merging is required.
2043 wfDebug( __METHOD__ .
": Suppressing edit conflict, same user.\n" );
2044 $this->isConflict =
false;
2049 if ( $this->sectiontitle !==
'' ) {
2057 if ( $this->isConflict ) {
2059 .
": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2060 .
" (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2063 if ( $this->editRevId !==
null ) {
2064 $content = $this->page->replaceSectionAtRev(
2071 $content = $this->page->replaceSectionContent(
2079 wfDebug( __METHOD__ .
": getting section '{$this->section}'\n" );
2080 $content = $this->page->replaceSectionContent(
2087 if ( is_null( $content ) ) {
2088 wfDebug( __METHOD__ .
": activating conflict; section replace failed.\n" );
2089 $this->isConflict =
true;
2090 $content = $textbox_content;
2091 } elseif ( $this->isConflict ) {
2095 $this->isConflict =
false;
2096 wfDebug( __METHOD__ .
": Suppressing edit conflict, successful merge.\n" );
2098 $this->section =
'';
2100 wfDebug( __METHOD__ .
": Keeping edit conflict, failed merge.\n" );
2104 if ( $this->isConflict ) {
2105 $status->setResult(
false, self::AS_CONFLICT_DETECTED );
2113 if ( $this->section ==
'new' ) {
2115 if ( !$this->allowBlankSummary && trim( $this->summary ) ==
'' ) {
2116 $this->missingSummary =
true;
2117 $status->fatal(
'missingsummary' );
2123 if ( $this->textbox1 ==
'' ) {
2124 $this->missingComment =
true;
2125 $status->fatal(
'missingcommenttext' );
2129 } elseif ( !$this->allowBlankSummary
2130 && !$content->equals( $this->getOriginalContent( $user ) )
2131 && !$content->isRedirect()
2132 && md5( $this->summary ) == $this->autoSumm
2134 $this->missingSummary =
true;
2135 $status->fatal(
'missingsummary' );
2141 $sectionanchor =
'';
2142 if ( $this->section ==
'new' ) {
2144 } elseif ( $this->section !=
'' ) {
2145 # Try to get a section anchor from the section source, redirect
2146 # to edited section if header found.
2147 # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2148 # for duplicate heading checking and maybe parsing.
2149 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
2150 # We can't deal with anchors, includes, html etc in the header for now,
2151 # headline would need to be parsed to improve this.
2152 if ( $hasmatch && strlen(
$matches[2] ) > 0 ) {
2156 $result[
'sectionanchor'] = $sectionanchor;
2162 $this->textbox1 = $this->
toEditText( $content );
2163 $this->section =
'';
2168 if ( !$this->allowSelfRedirect
2169 && $content->isRedirect()
2170 && $content->getRedirectTarget()->equals( $this->getTitle() )
2174 if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2175 $this->selfRedirect =
true;
2176 $status->fatal(
'selfredirect' );
2183 $this->contentLength = strlen( $this->
toEditText( $content ) );
2184 if ( $this->contentLength > $maxArticleSize * 1024 ) {
2185 $this->tooBig =
true;
2186 $status->setResult(
false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2192 ( ( $this->minoredit && !$this->isNew ) ?
EDIT_MINOR : 0 ) |
2195 $doEditStatus = $this->page->doEditContent(
2201 $content->getDefaultFormat(),
2206 if ( !$doEditStatus->isOK() ) {
2210 $errors = $doEditStatus->getErrorsArray();
2211 if ( in_array( $errors[0][0],
2212 [
'edit-gone-missing',
'edit-conflict',
'edit-already-exists' ] )
2214 $this->isConflict =
true;
2218 return $doEditStatus;
2221 $result[
'nullEdit'] = $doEditStatus->hasMessage(
'edit-no-change' );
2222 if ( $result[
'nullEdit'] ) {
2224 $user->pingLimiter(
'linkpurge' );
2226 $result[
'redirect'] = $content->isRedirect();
2231 if ( $changingContentModel ) {
2234 $new ?
false : $oldContentModel,
2250 $new = $oldModel ===
false;
2251 $log =
new ManualLogEntry(
'contentmodel', $new ?
'new' :
'change' );
2253 $log->setTarget( $this->mTitle );
2254 $log->setComment( $reason );
2255 $log->setParameters( [
2256 '4::oldmodel' => $oldModel,
2257 '5::newmodel' => $newModel
2259 $logid = $log->insert();
2260 $log->publish( $logid );
2267 $user = $this->context->getUser();
2268 if ( !$user->isLoggedIn() ) {
2275 DeferredUpdates::addCallableUpdate(
function () use ( $user, $title, $watch ) {
2276 if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2299 $baseContent = $baseRevision ? $baseRevision->getContent() :
null;
2301 if ( is_null( $baseContent ) ) {
2307 $currentContent = $currentRevision ? $currentRevision->getContent() :
null;
2309 if ( is_null( $currentContent ) ) {
2315 $result =
$handler->merge3( $baseContent, $editContent, $currentContent );
2320 $this->parentRevId = $currentRevision->getId();
2333 if ( !$this->mBaseRevision ) {
2335 $this->mBaseRevision = $this->editRevId
2377 if ( preg_match( $regex, $text,
$matches ) ) {
2385 $out = $this->context->getOutput();
2387 $out->addModules(
'mediawiki.action.edit' );
2388 $out->addModuleStyles(
'mediawiki.action.edit.styles' );
2390 $user = $this->context->getUser();
2391 if ( $user->getOption(
'showtoolbar' ) ) {
2396 $out->addModules(
'mediawiki.toolbar' );
2399 if ( $user->getOption(
'uselivepreview' ) ) {
2400 $out->addModules(
'mediawiki.action.edit.preview' );
2403 if ( $user->getOption(
'useeditwarning' ) ) {
2404 $out->addModules(
'mediawiki.action.edit.editWarning' );
2407 # Enabled article-related sidebar, toplinks, etc.
2408 $out->setArticleRelated(
true );
2411 if ( $this->isConflict ) {
2412 $msg =
'editconflict';
2413 } elseif ( $contextTitle->exists() && $this->section !=
'' ) {
2414 $msg = $this->section ==
'new' ?
'editingcomment' :
'editingsection';
2416 $msg = $contextTitle->exists()
2417 || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2418 && $contextTitle->getDefaultMessageText() !==
false
2424 # Use the title defined by DISPLAYTITLE magic word when present
2425 # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2426 # setPageTitle() treats the input as wikitext, which should be safe in either case.
2427 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() :
false;
2428 if ( $displayTitle ===
false ) {
2429 $displayTitle = $contextTitle->getPrefixedText();
2431 $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2432 # Transmit the name of the message to JavaScript for live preview
2433 # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2434 $out->addJsConfigVars( [
2435 'wgEditMessage' => $msg,
2436 'wgAjaxEditStash' => $this->context->getConfig()->get(
'AjaxEditStash' ),
2444 if ( $this->suppressIntro ) {
2448 $out = $this->context->getOutput();
2449 $namespace = $this->mTitle->getNamespace();
2451 if ( $namespace == NS_MEDIAWIKI ) {
2452 # Show a warning if editing an interface message
2453 $out->wrapWikiMsg(
"<div class='mw-editinginterface'>\n$1\n</div>",
'editinginterface' );
2454 # If this is a default message (but not css or js),
2455 # show a hint that it is translatable on translatewiki.net
2459 $defaultMessageText = $this->mTitle->getDefaultMessageText();
2460 if ( $defaultMessageText !==
false ) {
2461 $out->wrapWikiMsg(
"<div class='mw-translateinterface'>\n$1\n</div>",
2462 'translateinterface' );
2465 } elseif ( $namespace ==
NS_FILE ) {
2466 # Show a hint to shared repo
2468 if ( $file && !$file->isLocal() ) {
2469 $descUrl = $file->getDescriptionUrl();
2470 # there must be a description url to show a hint to shared repo
2472 if ( !$this->mTitle->exists() ) {
2473 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2474 'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2477 $out->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2478 'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2485 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2486 # Show log extract when the user is currently blocked
2487 if ( $namespace == NS_USER || $namespace ==
NS_USER_TALK ) {
2488 $username = explode(
'/', $this->mTitle->getText(), 2 )[0];
2489 $user = User::newFromName(
$username,
false );
2492 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { #
User does not exist
2493 $out->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2496 # Show log extract if the user is currently blocked
2500 MWNamespace::getCanonicalName( NS_USER ) .
':' . $block->getTarget(),
2504 'showIfEmpty' =>
false,
2506 'blocked-notice-logextract',
2507 $user->getName() # Support GENDER in notice
2513 # Try to add a custom edit intro, or use the standard one if this is not possible.
2516 $this->context->msg(
'helppage' )->inContentLanguage()->text()
2518 if ( $this->context->getUser()->isLoggedIn() ) {
2521 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2530 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2532 'newarticletextanon',
2538 # Give a notice if the user is editing a deleted/moved page...
2539 if ( !$this->mTitle->exists() ) {
2546 'conds' => [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ],
2547 'showIfEmpty' =>
false,
2548 'msgKey' => [
'recreate-moveddeleted-warn' ]
2560 if ( $this->editintro ) {
2561 $title = Title::newFromText( $this->editintro );
2562 if ( $title instanceof
Title && $title->
exists() && $title->userCan(
'read' ) ) {
2564 $this->context->getOutput()->addWikiTextTitleTidy(
2565 '<div class="mw-editintro">{{:' . $title->getFullText() .
'}}</div>',
2593 if ( $content ===
null || $content ===
false || is_string( $content ) ) {
2598 throw new MWException(
'This content model is not supported: ' . $content->getModel() );
2601 return $content->serialize( $this->contentFormat );
2621 if ( $text ===
false || $text ===
null ) {
2626 $this->contentModel, $this->contentFormat );
2629 throw new MWException(
'This content model is not supported: ' . $content->getModel() );
2644 # need to parse the preview early so that we know which templates are used,
2645 # otherwise users with "show preview after edit box" will get a blank list
2646 # we parse this near the beginning so that setHeaders can do the title
2647 # setting work instead of leaving it in getPreviewText
2648 $previewOutput =
'';
2649 if ( $this->formtype ==
'preview' ) {
2653 $out = $this->context->getOutput();
2657 Hooks::run(
'EditPage::showEditForm:initial', [ &$editPage, &
$out ] );
2664 if ( !$this->isConflict &&
2665 $this->section !=
'' &&
2670 $out->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
2676 $out->addHTML( $this->editFormPageTop );
2678 $user = $this->context->getUser();
2679 if ( $user->getOption(
'previewontop' ) ) {
2683 $out->addHTML( $this->editFormTextTop );
2685 $showToolbar =
true;
2687 if ( $this->formtype ==
'save' ) {
2690 $showToolbar =
false;
2692 $out->wrapWikiMsg(
"<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2693 'deletedwhileediting' );
2699 $out->addHTML( Html::openElement(
2702 'class' =>
'mw-editform',
2703 'id' => self::EDITFORM_ID,
2704 'name' => self::EDITFORM_ID,
2707 'enctype' =>
'multipart/form-data'
2711 if ( is_callable( $formCallback ) ) {
2712 wfWarn(
'The $formCallback parameter to ' . __METHOD__ .
'is deprecated' );
2713 call_user_func_array( $formCallback, [ &
$out ] );
2717 $out->addHTML( Html::hidden(
'wpUnicodeCheck', self::UNICODE_CHECK ) );
2721 Xml::openElement(
'div', [
'id' =>
'antispam-container',
'style' =>
'display: none;' ] )
2724 [
'for' =>
'wpAntispam' ],
2725 $this->context->msg(
'simpleantispam-label' )->parse()
2731 'name' =>
'wpAntispam',
2732 'id' =>
'wpAntispam',
2736 . Xml::closeElement(
'div' )
2741 Hooks::run(
'EditPage::showEditForm:fields', [ &$editPage, &
$out ] );
2747 $username = $this->lastDelete->user_name;
2752 $key = $comment ===
''
2753 ?
'confirmrecreate-noreason'
2754 :
'confirmrecreate';
2756 '<div class="mw-confirm-recreate">' .
2757 $this->context->msg( $key,
$username,
"<nowiki>$comment</nowiki>" )->parse() .
2758 Xml::checkLabel( $this->context->msg(
'recreate' )->text(),
'wpRecreate',
'wpRecreate',
false,
2765 # When the summary is hidden, also hide them on preview/show changes
2766 if ( $this->nosummary ) {
2767 $out->addHTML( Html::hidden(
'nosummary',
true ) );
2770 # If a blank edit summary was previously provided, and the appropriate
2771 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2772 # user being bounced back more than once in the event that a summary
2775 # For a bit more sophisticated detection of blank summaries, hash the
2776 # automatic one and pass that in the hidden field wpAutoSummary.
2777 if ( $this->missingSummary || ( $this->section ==
'new' && $this->nosummary ) ) {
2778 $out->addHTML( Html::hidden(
'wpIgnoreBlankSummary',
true ) );
2781 if ( $this->undidRev ) {
2782 $out->addHTML( Html::hidden(
'wpUndidRevision', $this->undidRev ) );
2785 if ( $this->selfRedirect ) {
2786 $out->addHTML( Html::hidden(
'wpIgnoreSelfRedirect',
true ) );
2789 if ( $this->hasPresetSummary ) {
2793 $this->autoSumm = md5(
'' );
2796 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2797 $out->addHTML( Html::hidden(
'wpAutoSummary', $autosumm ) );
2799 $out->addHTML( Html::hidden(
'oldid', $this->oldid ) );
2802 $out->addHTML( Html::hidden(
'format', $this->contentFormat ) );
2803 $out->addHTML( Html::hidden(
'model', $this->contentModel ) );
2807 if ( $this->section ==
'new' ) {
2812 $out->addHTML( $this->editFormTextBeforeContent );
2814 if ( !$this->mTitle->isCssJsSubpage() && $showToolbar && $user->getOption(
'showtoolbar' ) ) {
2815 $out->addHTML( self::getEditToolbar( $this->mTitle ) );
2818 if ( $this->blankArticle ) {
2819 $out->addHTML( Html::hidden(
'wpIgnoreBlankArticle',
true ) );
2822 if ( $this->isConflict ) {
2830 $this->textbox1 = $this->
toEditText( $content );
2837 $out->addHTML( $this->editFormTextAfterContent );
2847 $out->addHTML( $this->editFormTextAfterTools .
"\n" );
2851 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'hiddencats' ],
2854 $out->addHTML( Html::rawElement(
'div', [
'class' =>
'limitreport' ],
2855 self::getPreviewLimitReport( $this->mParserOutput ) ) );
2857 $out->addModules(
'mediawiki.action.edit.collapsibleFooter' );
2859 if ( $this->isConflict ) {
2864 $msg = $this->context->msg(
2865 'content-failed-to-parse',
2866 $this->contentModel,
2867 $this->contentFormat,
2870 $out->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
2875 if ( $this->isConflict ) {
2877 } elseif ( $this->preview ) {
2879 } elseif ( $this->
diff ) {
2884 $out->addHTML( Html::hidden(
'mode', $mode, [
'id' =>
'mw-edit-mode' ] ) );
2888 $out->addHTML( Html::hidden(
'wpUltimateParam',
true ) );
2889 $out->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
2891 if ( !$user->getOption(
'previewontop' ) ) {
2905 $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2910 if ( $this->preview ) {
2912 } elseif ( $this->section !=
'' ) {
2916 return Html::rawElement(
'div', [
'class' =>
'templatesUsed' ],
2917 $templateListFormatter->format( $templates,
$type )
2928 preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches );
2938 $out = $this->context->getOutput();
2939 $user = $this->context->getUser();
2940 if ( $this->isConflict ) {
2942 $this->editRevId = $this->page->getLatest();
2944 if ( $this->section !=
'' && $this->section !=
'new' ) {
2945 if ( !$this->summary && !$this->preview && !$this->
diff ) {
2947 if ( $sectionTitle !==
false ) {
2948 $this->summary =
"/* $sectionTitle */ ";
2955 if ( $this->missingComment ) {
2956 $out->wrapWikiMsg(
"<div id='mw-missingcommenttext'>\n$1\n</div>",
'missingcommenttext' );
2959 if ( $this->missingSummary && $this->section !=
'new' ) {
2961 "<div id='mw-missingsummary'>\n$1\n</div>",
2962 [
'missingsummary', $buttonLabel ]
2966 if ( $this->missingSummary && $this->section ==
'new' ) {
2968 "<div id='mw-missingcommentheader'>\n$1\n</div>",
2969 [
'missingcommentheader', $buttonLabel ]
2973 if ( $this->blankArticle ) {
2975 "<div id='mw-blankarticle'>\n$1\n</div>",
2976 [
'blankarticle', $buttonLabel ]
2980 if ( $this->selfRedirect ) {
2982 "<div id='mw-selfredirect'>\n$1\n</div>",
2983 [
'selfredirect', $buttonLabel ]
2987 if ( $this->hookError !==
'' ) {
2988 $out->addWikiText( $this->hookError );
2991 if ( $this->section !=
'new' ) {
2992 $revision = $this->mArticle->getRevisionFetched();
2998 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2999 'rev-deleted-text-permission'
3003 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3004 'rev-deleted-text-view'
3008 if ( !$revision->isCurrent() ) {
3009 $this->mArticle->setOldSubtitle( $revision->getId() );
3010 $out->addWikiMsg(
'editingold' );
3011 $this->isOldRev =
true;
3013 } elseif ( $this->mTitle->exists() ) {
3016 $out->wrapWikiMsg(
"<div class='errorbox'>\n$1\n</div>\n",
3017 [
'missing-revision', $this->oldid ] );
3024 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3027 } elseif ( $user->isAnon() ) {
3028 if ( $this->formtype !=
'preview' ) {
3030 "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3031 [
'anoneditwarning',
3034 'returnto' => $this->
getTitle()->getPrefixedDBkey()
3038 'returnto' => $this->
getTitle()->getPrefixedDBkey()
3043 $out->wrapWikiMsg(
"<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3044 'anonpreviewwarning'
3048 if ( $this->mTitle->isCssJsSubpage() ) {
3049 # Check the skin exists
3052 "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
3053 [
'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
3056 if ( $this->
getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3058 $out->wrapWikiMsg(
'<div class="mw-usercssjspublic">$1</div>',
3061 if ( $this->formtype !==
'preview' ) {
3062 $config = $this->context->getConfig();
3065 "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3066 [
'usercssyoucanpreview' ]
3070 if ( $this->mTitle->isJsSubpage() && $config->get(
'AllowUserJs' ) ) {
3072 "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3073 [
'userjsyoucanpreview' ]
3085 # Add header copyright warning
3098 return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3099 'id' =>
'wpSummary',
3100 'name' =>
'wpSummary',
3101 'maxlength' =>
'200',
3104 'spellcheck' =>
'true',
3123 $inputAttrs =
null, $spanLabelAttrs =
null
3129 $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
3130 'class' => $this->missingSummary ?
'mw-summarymissed' :
'mw-summary',
3131 'id' =>
"wpSummaryLabel"
3138 $inputAttrs[
'id'] ? [
'for' => $inputAttrs[
'id'] ] :
null,
3141 $label = Xml::tags(
'span', $spanLabelAttrs, $label );
3144 $input = Html::input(
'wpSummary',
$summary,
'text', $inputAttrs );
3146 return [ $label,
$input ];
3174 $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3183 $inputAttrs[
'inputId'] = $inputAttrs[
'id'];
3184 $inputAttrs[
'id'] =
'wpSummaryWidget';
3186 return new OOUI\FieldLayout(
3187 new OOUI\TextInputWidget( [
3189 'infusable' =>
true,
3192 'label' =>
new OOUI\HtmlSnippet( $labelText ),
3194 'id' =>
'wpSummaryLabel',
3195 'classes' => [ $this->missingSummary ?
'mw-summarymissed' :
'mw-summary' ],
3207 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3208 $summaryClass = $this->missingSummary ?
'mw-summarymissed' :
'mw-summary';
3209 if ( $isSubjectPreview ) {
3210 if ( $this->nosummary ) {
3214 if ( !$this->mShowSummaryField ) {
3219 $labelText = $this->context->msg( $isSubjectPreview ?
'subject' :
'summary' )->parse();
3223 [
'class' => $summaryClass ]
3237 if ( !
$summary || ( !$this->preview && !$this->
diff ) ) {
3243 if ( $isSubjectPreview ) {
3244 $summary = $this->context->msg(
'newsectionsummary' )
3246 ->inContentLanguage()->text();
3249 $message = $isSubjectPreview ?
'subject-preview' :
'summary-preview';
3251 $summary = $this->context->msg( $message )->parse()
3253 return Xml::tags(
'div', [
'class' =>
'mw-summary-preview' ],
$summary );
3257 $out = $this->context->getOutput();
3258 $out->addHTML( Html::hidden(
'wpSection', $this->section ) );
3259 $out->addHTML( Html::hidden(
'wpStarttime', $this->starttime ) );
3260 $out->addHTML( Html::hidden(
'wpEdittime', $this->edittime ) );
3261 $out->addHTML( Html::hidden(
'editRevId', $this->editRevId ) );
3262 $out->addHTML( Html::hidden(
'wpScrolltop', $this->scrolltop, [
'id' =>
'wpScrolltop' ] ) );
3278 $this->context->getOutput()->addHTML(
3280 Html::hidden(
"wpEditToken", $this->context->getUser()->getEditToken() ) .
3307 $attribs = [
'style' =>
'display:none;' ];
3310 if ( $this->mTitle->isProtected(
'edit' ) &&
3311 MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [
'' ]
3313 # Is the title semi-protected?
3314 if ( $this->mTitle->isSemiProtected() ) {
3315 $classes[] =
'mw-textarea-sprotected';
3317 # Then it must be protected based on static groups (regular)
3318 $classes[] =
'mw-textarea-protected';
3320 # Is the title cascade-protected?
3321 if ( $this->mTitle->isCascadeProtected() ) {
3322 $classes[] =
'mw-textarea-cprotected';
3325 # Is an old revision being edited?
3326 if ( $this->isOldRev ) {
3327 $classes[] =
'mw-textarea-oldrev';
3336 if ( count( $classes ) ) {
3337 if ( isset(
$attribs[
'class'] ) ) {
3340 $attribs[
'class'] = implode(
' ', $classes );
3352 $this->
showTextbox( $this->textbox2,
'wpTextbox2', [
'tabindex' => 6,
'readonly' ] );
3360 $this->context->getOutput()->addHTML( Html::textarea( $name, $wikitext,
$attribs ) );
3366 $classes[] =
'ontop';
3369 $attribs = [
'id' =>
'wikiPreview',
'class' => implode(
' ', $classes ) ];
3371 if ( $this->formtype !=
'preview' ) {
3372 $attribs[
'style'] =
'display: none;';
3375 $out = $this->context->getOutput();
3378 if ( $this->formtype ==
'preview' ) {
3382 $pageViewLang = $this->mTitle->getPageViewLanguage();
3383 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3384 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3388 $out->addHTML(
'</div>' );
3390 if ( $this->formtype ==
'diff' ) {
3394 $msg = $this->context->msg(
3395 'content-failed-to-parse',
3396 $this->contentModel,
3397 $this->contentFormat,
3400 $out->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
3413 $this->mArticle->openShowCategory();
3415 # This hook seems slightly odd here, but makes things more
3416 # consistent for extensions.
3417 $out = $this->context->getOutput();
3418 Hooks::run(
'OutputPageBeforeHTML', [ &
$out, &$text ] );
3419 $out->addHTML( $text );
3421 $this->mArticle->closeShowCategory();
3435 $oldtitlemsg =
'currentrev';
3436 # if message does not exist, show diff against the preloaded default
3437 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3438 $oldtext = $this->mTitle->getDefaultMessageText();
3439 if ( $oldtext !==
false ) {
3440 $oldtitlemsg =
'defaultmessagetext';
3450 if ( $this->editRevId !==
null ) {
3451 $newContent = $this->page->replaceSectionAtRev(
3452 $this->section, $textboxContent, $this->summary, $this->editRevId
3455 $newContent = $this->page->replaceSectionContent(
3456 $this->section, $textboxContent, $this->summary, $this->edittime
3460 if ( $newContent ) {
3461 Hooks::run(
'EditPageGetDiffContent', [ $this, &$newContent ] );
3463 $user = $this->context->getUser();
3464 $popts = ParserOptions::newFromUserAndLang( $user,
$wgContLang );
3465 $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3468 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3469 $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3470 $newtitle = $this->context->msg(
'yourtext' )->parse();
3472 if ( !$oldContent ) {
3473 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3476 if ( !$newContent ) {
3477 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3480 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3481 $de->setContent( $oldContent, $newContent );
3483 $difftext = $de->getDiff( $oldtitle, $newtitle );
3484 $de->showDiffStyle();
3489 $this->context->getOutput()->addHTML(
'<div id="wikiDiff">' . $difftext .
'</div>' );
3496 $msg =
'editpage-head-copy-warn';
3497 if ( !$this->context->msg( $msg )->isDisabled() ) {
3498 $this->context->getOutput()->wrapWikiMsg(
"<div class='editpage-head-copywarn'>\n$1\n</div>",
3499 'editpage-head-copy-warn' );
3512 $msg =
'editpage-tos-summary';
3513 Hooks::run(
'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3514 if ( !$this->context->msg( $msg )->isDisabled() ) {
3515 $out = $this->context->getOutput();
3516 $out->addHTML(
'<div class="mw-tos-summary">' );
3517 $out->addWikiMsg( $msg );
3518 $out->addHTML(
'</div>' );
3527 $this->context->getOutput()->addHTML(
'<div class="mw-editTools">' .
3528 $this->context->msg(
'edittools' )->inContentLanguage()->parse() .
3553 $copywarnMsg = [
'copyrightwarning',
3554 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]',
3557 $copywarnMsg = [
'copyrightwarning2',
3558 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->text() .
']]' ];
3561 Hooks::run(
'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3563 $msg = call_user_func_array(
'wfMessage', $copywarnMsg )->title( $title );
3565 $msg->inLanguage( $langcode );
3567 return "<div id=\"editpage-copywarn\">\n" .
3568 $msg->$format() .
"\n</div>";
3583 $limitReport = Html::rawElement(
'div', [
'class' =>
'mw-limitReportExplanation' ],
3584 wfMessage(
'limitreport-title' )->parseAsBlock()
3588 $limitReport .= Html::openElement(
'div', [
'class' =>
'preview-limit-report-wrapper' ] );
3590 $limitReport .= Html::openElement(
'table', [
3591 'class' =>
'preview-limit-report wikitable'
3593 Html::openElement(
'tbody' );
3595 foreach (
$output->getLimitReportData() as $key =>
$value ) {
3596 if ( Hooks::run(
'ParserLimitReportFormat',
3597 [ $key, &
$value, &$limitReport,
true,
true ]
3600 $valueMsg =
wfMessage( [
"$key-value-html",
"$key-value" ] );
3601 if ( !$valueMsg->exists() ) {
3604 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3605 $limitReport .= Html::openElement(
'tr' ) .
3606 Html::rawElement(
'th',
null, $keyMsg->parse() ) .
3607 Html::rawElement(
'td',
null, $valueMsg->params(
$value )->parse() ) .
3608 Html::closeElement(
'tr' );
3613 $limitReport .= Html::closeElement(
'tbody' ) .
3614 Html::closeElement(
'table' ) .
3615 Html::closeElement(
'div' );
3617 return $limitReport;
3621 $out = $this->context->getOutput();
3622 $out->addHTML(
"<div class='editOptions'>\n" );
3624 if ( $this->section !=
'new' ) {
3631 [
'minor' => $this->minoredit,
'watch' => $this->watchthis ]
3633 $checkboxesHTML =
new OOUI\HorizontalLayout( [
'items' => $checkboxes ] );
3635 $out->addHTML(
"<div class='editCheckboxes'>" . $checkboxesHTML .
"</div>\n" );
3639 $out->addHTML( $this->editFormTextAfterWarn );
3641 $out->addHTML(
"<div class='editButtons'>\n" );
3645 if ( $cancel !==
'' ) {
3646 $cancel .= Html::element(
'span',
3647 [
'class' =>
'mw-editButtons-pipe-separator' ],
3648 $this->context->msg(
'pipe-separator' )->text() );
3651 $message = $this->context->msg(
'edithelppage' )->inContentLanguage()->text();
3655 $this->context->msg(
'edithelp' )->text(),
3656 [
'target' =>
'helpwindow',
'href' => $edithelpurl ],
3659 $this->context->msg(
'word-separator' )->escaped() .
3660 $this->context->msg(
'newwindow' )->parse();
3662 $out->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3663 $out->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3664 $out->addHTML(
"</div><!-- editButtons -->\n" );
3666 Hooks::run(
'EditPage::showStandardInputs:options', [ $this,
$out, &
$tabindex ] );
3668 $out->addHTML(
"</div><!-- editOptions -->\n" );
3676 $out = $this->context->getOutput();
3679 if ( Hooks::run(
'EditPageBeforeConflictDiff', [ &$editPage, &
$out ] ) ) {
3682 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
3688 $de =
$handler->createDifferenceEngine( $this->context );
3689 $de->setContent( $content2, $content1 );
3691 $this->context->msg(
'yourtext' )->parse(),
3692 $this->context->msg(
'storedversion' )->text()
3695 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
3701 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3702 $stats->increment(
'edit.failures.conflict' );
3705 $this->mTitle->getNamespace() >=
NS_MAIN &&
3708 $stats->increment(
'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
3717 if ( !$this->isConflict && $this->oldid > 0 ) {
3720 $cancelParams[
'redirect'] =
'no';
3723 return new OOUI\ButtonWidget( [
3724 'id' =>
'mw-editform-cancel',
3726 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
'cancel' )->parse() ),
3728 'infusable' =>
true,
3729 'flags' =>
'destructive',
3743 return $title->getLocalURL( [
'action' => $this->
action ] );
3754 if ( $this->deletedSinceEdit !==
null ) {
3758 $this->deletedSinceEdit =
false;
3760 if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3762 if ( $this->lastDelete ) {
3763 $deleteTime =
wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3764 if ( $deleteTime > $this->starttime ) {
3765 $this->deletedSinceEdit =
true;
3779 $data =
$dbr->selectRow(
3780 [
'logging',
'user' ] + $commentQuery[
'tables'],
3791 ] + $commentQuery[
'fields'], [
3792 'log_namespace' => $this->mTitle->getNamespace(),
3793 'log_title' => $this->mTitle->getDBkey(),
3794 'log_type' =>
'delete',
3795 'log_action' =>
'delete',
3799 [
'LIMIT' => 1,
'ORDER BY' =>
'log_timestamp DESC' ],
3801 'user' => [
'JOIN',
'user_id=log_user' ],
3802 ] + $commentQuery[
'joins']
3805 if ( is_object( $data ) ) {
3807 $data->user_name = $this->context->msg(
'rev-deleted-user' )->escaped();
3811 $data->log_comment_text = $this->context->msg(
'rev-deleted-comment' )->escaped();
3812 $data->log_comment_data =
null;
3825 $out = $this->context->getOutput();
3826 $config = $this->context->getConfig();
3828 if ( $config->get(
'RawHtml' ) && !$this->mTokenOk ) {
3832 if ( $this->textbox1 !==
'' ) {
3836 $parsedNote =
$out->parse(
"<div class='previewnote'>" .
3837 $this->context->msg(
'session_fail_preview_html' )->text() .
"</div>",
3851 'AlternateEditPreview',
3852 [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3854 return $previewHTML;
3857 # provide a anchor link to the editform
3858 $continueEditing =
'<span class="mw-continue-editing">' .
3859 '[[#' . self::EDITFORM_ID .
'|' .
3860 $this->context->getLanguage()->getArrow() .
' ' .
3861 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
3862 if ( $this->mTriedSave && !$this->mTokenOk ) {
3863 if ( $this->mTokenOkExceptSuffix ) {
3864 $note = $this->context->msg(
'token_suffix_mismatch' )->plain();
3867 $note = $this->context->msg(
'session_fail_preview' )->plain();
3870 } elseif ( $this->incompleteForm ) {
3871 $note = $this->context->msg(
'edit_form_incomplete' )->plain();
3872 if ( $this->mTriedSave ) {
3876 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
3879 # don't parse non-wikitext pages, show message about preview
3880 if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3881 if ( $this->mTitle->isCssJsSubpage() ) {
3883 } elseif ( $this->mTitle->isCssOrJsPage() ) {
3891 if ( $level ===
'user' && !$config->get(
'AllowUserCss' ) ) {
3896 if ( $level ===
'user' && !$config->get(
'AllowUserJs' ) ) {
3903 # Used messages to make sure grep find them:
3904 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3905 if ( $level && $format ) {
3906 $note =
"<div id='mw-{$level}{$format}preview'>" .
3907 $this->context->msg(
"{$level}{$format}preview" )->text() .
3908 ' ' . $continueEditing .
"</div>";
3912 # If we're adding a comment, we need to show the
3913 # summary as the headline
3914 if ( $this->section ===
"new" && $this->summary !==
"" ) {
3915 $content = $content->addSectionHeader( $this->summary );
3918 $hook_args = [ $this, &$content ];
3919 Hooks::run(
'EditPageGetPreviewContent', $hook_args );
3922 $parserOutput = $parserResult[
'parserOutput'];
3923 $previewHTML = $parserResult[
'html'];
3924 $this->mParserOutput = $parserOutput;
3925 $out->addParserOutputMetadata( $parserOutput );
3927 if ( count( $parserOutput->getWarnings() ) ) {
3928 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
3932 $m = $this->context->msg(
3933 'content-failed-to-parse',
3934 $this->contentModel,
3935 $this->contentFormat,
3938 $note .=
"\n\n" . $m->parse();
3942 if ( $this->isConflict ) {
3943 $conflict =
'<h2 id="mw-previewconflict">'
3944 . $this->context->msg(
'previewconflict' )->escaped() .
"</h2>\n";
3946 $conflict =
'<hr />';
3949 $previewhead =
"<div class='previewnote'>\n" .
3950 '<h2 id="mw-previewheader">' . $this->context->msg(
'preview' )->escaped() .
"</h2>" .
3951 $out->parse( $note,
true,
true ) . $conflict .
"</div>\n";
3953 $pageViewLang = $this->mTitle->getPageViewLanguage();
3954 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3955 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
3956 $previewHTML = Html::rawElement(
'div',
$attribs, $previewHTML );
3962 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3963 $stats->increment(
'edit.failures.' . $failureType );
3971 $parserOptions = $this->page->makeParserOptions( $this->context );
3972 $parserOptions->setIsPreview(
true );
3973 $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !==
'' );
3974 $parserOptions->enableLimitReport();
3975 return $parserOptions;
3988 $user = $this->context->getUser();
3990 $pstContent = $content->
preSaveTransform( $this->mTitle, $user, $parserOptions );
3991 $scopedCallback = $parserOptions->setupFakeRevision(
3992 $this->mTitle, $pstContent, $user );
3993 $parserOutput = $pstContent->getParserOutput( $this->mTitle,
null, $parserOptions );
3994 ScopedCallback::consume( $scopedCallback );
3995 $parserOutput->setEditSectionTokens(
false );
3997 'parserOutput' => $parserOutput,
3998 'html' => $parserOutput->getText() ];
4005 if ( $this->preview || $this->section !=
'' ) {
4007 if ( !isset( $this->mParserOutput ) ) {
4010 foreach ( $this->mParserOutput->getTemplates() as $ns =>
$template ) {
4011 foreach ( array_keys(
$template ) as $dbk ) {
4012 $templates[] = Title::makeTitle( $ns, $dbk );
4017 return $this->mTitle->getTemplateLinksFrom();
4033 $showSignature =
true;
4035 $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
4049 'id' =>
'mw-editbutton-bold',
4051 'close' =>
'\'\
'\'',
4052 'sample' =>
wfMessage(
'bold_sample' )->text(),
4053 'tip' =>
wfMessage(
'bold_tip' )->text(),
4056 'id' =>
'mw-editbutton-italic',
4059 'sample' =>
wfMessage(
'italic_sample' )->text(),
4060 'tip' =>
wfMessage(
'italic_tip' )->text(),
4063 'id' =>
'mw-editbutton-link',
4066 'sample' =>
wfMessage(
'link_sample' )->text(),
4067 'tip' =>
wfMessage(
'link_tip' )->text(),
4070 'id' =>
'mw-editbutton-extlink',
4073 'sample' =>
wfMessage(
'extlink_sample' )->text(),
4074 'tip' =>
wfMessage(
'extlink_tip' )->text(),
4077 'id' =>
'mw-editbutton-headline',
4080 'sample' =>
wfMessage(
'headline_sample' )->text(),
4081 'tip' =>
wfMessage(
'headline_tip' )->text(),
4083 $imagesAvailable ? [
4084 'id' =>
'mw-editbutton-image',
4087 'sample' =>
wfMessage(
'image_sample' )->text(),
4088 'tip' =>
wfMessage(
'image_tip' )->text(),
4090 $imagesAvailable ? [
4091 'id' =>
'mw-editbutton-media',
4094 'sample' =>
wfMessage(
'media_sample' )->text(),
4095 'tip' =>
wfMessage(
'media_tip' )->text(),
4098 'id' =>
'mw-editbutton-nowiki',
4099 'open' =>
"<nowiki>",
4100 'close' =>
"</nowiki>",
4101 'sample' =>
wfMessage(
'nowiki_sample' )->text(),
4102 'tip' =>
wfMessage(
'nowiki_tip' )->text(),
4105 'id' =>
'mw-editbutton-signature',
4106 'open' =>
wfMessage(
'sig-text',
'~~~~' )->inContentLanguage()->text(),
4109 'tip' =>
wfMessage(
'sig_tip' )->text(),
4112 'id' =>
'mw-editbutton-hr',
4113 'open' =>
"\n----\n",
4120 $script =
'mw.loader.using("mediawiki.toolbar", function () {';
4121 foreach ( $toolarray as $tool ) {
4140 $script .= Xml::encodeJsCall(
4141 'mw.toolbar.addButton',
4149 $toolbar =
'<div id="toolbar"></div>';
4151 if ( Hooks::run(
'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4181 $user = $this->context->getUser();
4183 if ( !$this->isNew && $user->isAllowed(
'minoredit' ) ) {
4184 $checkboxes[
'wpMinoredit'] = [
4185 'id' =>
'wpMinoredit',
4186 'label-message' =>
'minoredit',
4188 'tooltip' =>
'minoredit',
4189 'label-id' =>
'mw-editpage-minoredit',
4190 'legacy-name' =>
'minor',
4191 'default' => $checked[
'minor'],
4195 if ( $user->isLoggedIn() ) {
4196 $checkboxes[
'wpWatchthis'] = [
4197 'id' =>
'wpWatchthis',
4198 'label-message' =>
'watchthis',
4200 'tooltip' =>
'watch',
4201 'label-id' =>
'mw-editpage-watch',
4202 'legacy-name' =>
'watch',
4203 'default' => $checked[
'watch'],
4208 Hooks::run(
'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4229 if ( !$this->isNew ) {
4230 $checkboxes[
'minor'] =
'';
4232 $checkboxes[
'watch'] =
'';
4234 foreach ( $checkboxesDef as $name =>
$options ) {
4236 $label = $this->context->msg(
$options[
'label-message'] )->parse();
4244 if ( isset(
$options[
'tooltip'] ) ) {
4245 $attribs[
'accesskey'] = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4248 if ( isset(
$options[
'title-message'] ) ) {
4249 $labelAttribs[
'title'] = $this->context->msg(
$options[
'title-message'] )->text();
4251 if ( isset(
$options[
'label-id'] ) ) {
4252 $labelAttribs[
'id'] =
$options[
'label-id'];
4257 Xml::tags(
'label', $labelAttribs, $label );
4260 $checkboxHtml = Html::rawElement(
'div', [
'class' =>
'mw-ui-checkbox' ], $checkboxHtml );
4263 $checkboxes[ $legacyName ] = $checkboxHtml;
4268 Hooks::run(
'EditPageBeforeEditChecks', [ &$editPage, &$checkboxes, &
$tabindex ],
'1.29' );
4301 foreach ( $checkboxesDef as $name =>
$options ) {
4306 if ( isset(
$options[
'tooltip'] ) ) {
4307 $accesskey = $this->context->msg(
"accesskey-{$options['tooltip']}" )->text();
4310 if ( isset(
$options[
'title-message'] ) ) {
4311 $title = $this->context->msg(
$options[
'title-message'] )->text();
4314 $checkboxes[ $legacyName ] =
new OOUI\FieldLayout(
4315 new OOUI\CheckboxInputWidget( [
4317 'accessKey' => $accesskey,
4322 'infusable' =>
true,
4325 'align' =>
'inline',
4326 'label' =>
new OOUI\HtmlSnippet( $this->context->msg(
$options[
'label-message'] )->parse() ),
4336 $legacyCheckboxes = [];
4337 if ( !$this->isNew ) {
4338 $legacyCheckboxes[
'minor'] =
'';
4340 $legacyCheckboxes[
'watch'] =
'';
4342 foreach ( $checkboxes as $name => $oouiLayout ) {
4347 Hooks::run(
'EditPageBeforeEditChecks', [ &$ep, &$legacyCheckboxes, &
$tabindex ],
'1.29' );
4349 foreach ( $legacyCheckboxes as $name =>
$html ) {
4350 if (
$html && !isset( $checkboxes[$name] ) ) {
4351 $checkboxes[
$name] =
new OOUI\Widget( [
'content' =>
new OOUI\HtmlSnippet(
$html ) ] );
4366 $this->context->getConfig()->get(
'EditSubmitButtonLabelPublish' );
4369 $newPage = !$this->mTitle->exists();
4371 if ( $labelAsPublish ) {
4372 $buttonLabelKey = $newPage ?
'publishpage' :
'publishchanges';
4374 $buttonLabelKey = $newPage ?
'savearticle' :
'savechanges';
4377 return $buttonLabelKey;
4398 $saveConfig = OOUI\Element::configFromHtmlAttributes(
$attribs );
4399 $buttons[
'save'] =
new OOUI\ButtonInputWidget( [
4400 'id' =>
'wpSaveWidget',
4401 'inputId' =>
'wpSave',
4403 'useInputTag' =>
true,
4404 'flags' => [
'constructive',
'primary' ],
4405 'label' => $buttonLabel,
4406 'infusable' =>
true,
4413 'name' =>
'wpPreview',
4417 $previewConfig = OOUI\Element::configFromHtmlAttributes(
$attribs );
4418 $buttons[
'preview'] =
new OOUI\ButtonInputWidget( [
4419 'id' =>
'wpPreviewWidget',
4420 'inputId' =>
'wpPreview',
4422 'useInputTag' =>
true,
4423 'label' => $this->context->msg(
'showpreview' )->text(),
4424 'infusable' =>
true,
4428 ] + $previewConfig );
4435 $diffConfig = OOUI\Element::configFromHtmlAttributes(
$attribs );
4436 $buttons[
'diff'] =
new OOUI\ButtonInputWidget( [
4437 'id' =>
'wpDiffWidget',
4438 'inputId' =>
'wpDiff',
4440 'useInputTag' =>
true,
4441 'label' => $this->context->msg(
'showdiff' )->text(),
4442 'infusable' =>
true,
4450 Hooks::run(
'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &
$tabindex ] );
4460 $out = $this->context->getOutput();
4461 $out->prepareErrorPage( $this->context->msg(
'nosuchsectiontitle' ) );
4463 $res = $this->context->msg(
'nosuchsectiontext', $this->section )->parseAsBlock();
4467 Hooks::run(
'EditPageNoSuchSection', [ &$editPage, &
$res ] );
4470 $out->returnToMain(
false, $this->mTitle );
4481 if ( is_array( $match ) ) {
4482 $match = $this->context->getLanguage()->listToText( $match );
4484 $out = $this->context->getOutput();
4485 $out->prepareErrorPage( $this->context->msg(
'spamprotectiontitle' ) );
4487 $out->addHTML(
'<div id="spamprotected">' );
4488 $out->addWikiMsg(
'spamprotectiontext' );
4492 $out->addHTML(
'</div>' );
4494 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
4497 $out->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
4514 return rtrim(
$request->getText( $field ) );
4534 $out = $this->context->getOutput();
4535 $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4536 if ( count( $editNotices ) ) {
4537 $out->addHTML( implode(
"\n", $editNotices ) );
4539 $msg = $this->context->msg(
'editnotice-notext' );
4540 if ( !$msg->isDisabled() ) {
4542 '<div class="mw-editnotice-notext">'
4543 . $msg->parseAsBlock()
4554 if ( $this->mTitle->isTalkPage() ) {
4555 $this->context->getOutput()->addWikiMsg(
'talkpagetext' );
4563 if ( $this->contentLength ===
false ) {
4564 $this->contentLength = strlen( $this->textbox1 );
4567 $out = $this->context->getOutput();
4568 $lang = $this->context->getLanguage();
4569 $maxArticleSize = $this->context->getConfig()->get(
'MaxArticleSize' );
4570 if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4571 $out->wrapWikiMsg(
"<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4574 $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4575 $lang->formatNum( $maxArticleSize )
4579 if ( !$this->context->msg(
'longpage-hint' )->isDisabled() ) {
4580 $out->wrapWikiMsg(
"<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4583 $lang->formatSize( strlen( $this->textbox1 ) ),
4584 strlen( $this->textbox1 )
4595 $out = $this->context->getOutput();
4596 if ( $this->mTitle->isProtected(
'edit' ) &&
4597 MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [
'' ]
4599 # Is the title semi-protected?
4600 if ( $this->mTitle->isSemiProtected() ) {
4601 $noticeMsg =
'semiprotectedpagewarning';
4603 # Then it must be protected based on static groups (regular)
4604 $noticeMsg =
'protectedpagewarning';
4607 [
'lim' => 1,
'msgKey' => [ $noticeMsg ] ] );
4609 if ( $this->mTitle->isCascadeProtected() ) {
4610 # Is this page under cascading protection from some source pages?
4612 list( $cascadeSources, ) = $this->mTitle->getCascadeProtectionSources();
4613 $notice =
"<div class='mw-cascadeprotectedwarning'>\n$1\n";
4614 $cascadeSourcesCount = count( $cascadeSources );
4615 if ( $cascadeSourcesCount > 0 ) {
4616 # Explain, and list the titles responsible
4617 foreach ( $cascadeSources as
$page ) {
4618 $notice .=
'* [[:' .
$page->getPrefixedText() .
"]]\n";
4621 $notice .=
'</div>';
4622 $out->wrapWikiMsg( $notice, [
'cascadeprotectedwarning', $cascadeSourcesCount ] );
4624 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions(
'create' ) ) {
4627 'showIfEmpty' =>
false,
4628 'msgKey' => [
'titleprotectedwarning' ],
4629 'wrap' =>
"<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4639 "<div class='mw-explainconflict'>\n$1\n</div>",
4640 [
'explainconflict', $this->context->msg( $this->getSubmitButtonLabel() )->text() ]
4667 $class =
'mw-editfont-' . $user->getOption(
'editfont' );
4669 if ( isset(
$attribs[
'class'] ) ) {
4670 if ( is_string(
$attribs[
'class'] ) ) {
4672 } elseif ( is_array(
$attribs[
'class'] ) ) {
4679 $pageLang = $this->mTitle->getPageLanguage();
4680 $attribs[
'lang'] = $pageLang->getHtmlCode();
4681 $attribs[
'dir'] = $pageLang->getDir();
4692 if ( strval( $wikitext ) !==
'' ) {
4717 $userAgent = $this->context->getRequest()->getHeader(
'User-Agent' );
4718 if ( $userAgent && preg_match(
'/MSIE|Edge/', $userAgent ) ) {
4720 return $wgParser->guessLegacySectionNameFromWikiText( $text );
4723 return $wgParser->guessSectionNameFromWikiText( $text );
$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...
$wgEnableUploads
Uploads have to be specially set up to be secure.
$wgUseMediaWikiUIEverywhere
Temporary variable that applies MediaWiki UI wherever it can be supported.
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.
wfFindFile( $title, $options=[])
Find a file.
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.
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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'] !='') if(! $wgEnableAPI) $wgTitle
Class for viewing MediaWiki article and history.
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Special handling for category description pages, showing pages, subcategories and file that belong to...
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
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.
getCheckboxes(&$tabindex, $checked)
Returns an array of html code of the following checkboxes old style: minor and watch.
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
isOouiEnabled()
Check if the edit page is using OOUI controls.
safeUnicodeInput( $request, $field)
Filter an input field through a Unicode de-armoring process if it came from an old browser with known...
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.
static getPreviewLimitReport( $output)
Get the Limit report for page previews.
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...
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.
safeUnicodeOutput( $text)
Filter an output field through a Unicode armoring process if it is going to an old browser with known...
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
$editFormTextBeforeContent
__construct(Article $article)
getEditPermissionErrors( $rigor='secure')
showStandardInputs(&$tabindex=2)
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
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)
isWrongCaseCssJsPage()
Checks whether the user entered a skin name in uppercase, e.g.
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the neccessary 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.
string null $unicodeCheck
What the user submitted in the 'wpUnicodeCheck' field.
const AS_READ_ONLY_PAGE_LOGGED
Status: this logged in user is not allowed to edit this page.
initialiseForm()
Initialise form fields in the object Called on the first invocation, e.g.
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...
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)
bool $isWrongCaseCssJsPage
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)
getCurrentContent()
Get the current content of the page.
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.
bool $isNew
New page or new section.
showIntro()
Show all applicable editing introductions.
static getEditToolbar( $title=null)
Shows a bulletin board style toolbar for common editing functions.
getSummaryInputOOUI( $summary="", $labelText=null, $inputAttrs=null)
Builds a standard summary input with a label.
const AS_IMAGE_REDIRECT_ANON
Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
getContentObject( $def_content=null)
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".
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="")
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,...
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.
getSummaryInput( $summary="", $labelText=null, $inputAttrs=null, $spanLabelAttrs=null)
Standard summary input and label (wgSummary), abstracted so EditPage subclasses may reorganize the fo...
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
getCheckboxesOOUI(&$tabindex, $checked)
Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and any ot...
Revision bool $mBaseRevision
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)
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 commentBlock( $comment, $title=null, $local=false, $wikiId=null)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[])
Returns the attributes for the tooltip and access key.
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
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 log entries manually, to inject them into the database.
setPerformer(User $performer)
Set the user that performed the action being logged.
This class should be covered by a general architecture document which does not exist as of January 20...
Show an error when a user tries to do something they do not have the necessary permissions for.
Variant of the Message class.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie,...
static makeInlineScript( $script)
Construct an inline script tag with given JS code.
static loadFromTitle( $db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
static userWasLastToEdit( $db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page.
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
static getSkinNames()
Fetch the set of available skins.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
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.
exists( $flags=0)
Check if page exists.
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.
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
getRedirectTarget()
If this page is a redirect, get its target.
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
isRedirect()
Tests if the article content represents a redirect.
if( $wgSpamBlacklistFiles) $regexes
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
the array() calling protocol came about after MediaWiki 1.4rc1.
info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) action
namespace being checked & $result
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
also included in $newHeader if any indicating whether we should show just the diff
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
null means default & $customAttribs
namespace and then decline to actually register it file or subcat img or subcat $title
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "<div ...>$1</div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
it s the revision text itself In either if gzip is the revision text is gzipped $flags
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
in this case you re responsible for computing and outputting the entire conflict i the difference between revisions and your text headers and sections and Diff & $tabindex
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
this hook is for auditing only or null if authentication failed before getting that far $username
Allows to change the fields on the form that will be generated $name
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
this hook is for auditing only $response
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy: boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
processing should stop and the error should be shown to the user * false
returning false will NOT prevent logging $e
const CONTENT_MODEL_JAVASCRIPT
Base interface for content objects.
preSaveTransform(Title $title, User $user, ParserOptions $parserOptions)
Returns a Content object with pre-save transformations applied (or this object if no transformations ...
serialize( $format=null)
Convenience method for serializing this Content object.
Interface for objects which can provide a MediaWiki context on request.
if(is_array($mode)) switch( $mode) $input
if(!isset( $args[0])) $lang