46 protected int $undo = 0;
47 protected int $undoafter = 0;
48 protected int $cur = 0;
57 private bool $useRCPatrol;
77 parent::__construct( $article, $context );
78 $this->readOnlyMode = $readOnlyMode;
79 $this->revisionLookup = $revisionLookup;
80 $this->revisionRenderer = $revisionRenderer;
81 $this->commentFormatter = $commentFormatter;
82 $this->useRCPatrol = $config->
get( MainConfigNames::UseRCPatrol );
101 MediaWiki\Session\SessionManager::getGlobalSession()->persist();
107 $out->setRobotPolicy(
'noindex,nofollow' );
111 if ( $this->readOnlyMode->isReadOnly() ) {
113 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
114 [
'readonlywarning', $this->readOnlyMode->getReason() ]
116 } elseif ( $this->context->getUser()->isAnon() ) {
119 if ( !$this->
getRequest()->getCheck(
'wpPreview' ) ) {
125 SpecialPage::getTitleFor(
'Userlogin' )->getFullURL( [
126 'returnto' => $this->getTitle()->getPrefixedDBkey()
129 SpecialPage::getTitleFor(
'CreateAccount' )->getFullURL( [
130 'returnto' => $this->getTitle()->getPrefixedDBkey()
133 'mw-anon-edit-warning'
139 $out->msg(
'anonpreviewwarning' )->parse(),
140 'mw-anon-preview-warning'
150 $this->undoafter = $this->
getRequest()->getInt(
'undoafter' );
151 $this->undo = $this->
getRequest()->getInt(
'undo' );
153 if ( $this->undo == 0 || $this->undoafter == 0 ) {
154 throw new ErrorPageError(
'mcrundofailed',
'mcrundo-missingparam' );
162 $this->cur = $this->
getRequest()->getInt(
'cur', $this->curRev->getId() );
166 parent::checkCanExecute( $user );
172 $undoRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undo );
173 $oldRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undoafter );
175 if ( $undoRev ===
null || $oldRev ===
null ||
176 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
177 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
188 private function getNewRevision() {
189 $undoRev = $this->revisionLookup->getRevisionById( $this->undo );
190 $oldRev = $this->revisionLookup->getRevisionById( $this->undoafter );
191 $curRev = $this->curRev;
193 $isLatest = $curRev->
getId() === $undoRev->getId();
195 if ( $undoRev ===
null || $oldRev ===
null ||
196 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
197 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
204 return MutableRevisionRecord::newFromParentRevision( $oldRev );
207 $newRev = MutableRevisionRecord::newFromParentRevision( $curRev );
211 $rolesToMerge = array_unique( array_merge(
212 $oldRev->getSlotRoles(),
213 $undoRev->getSlotRoles(),
219 $rolesToMerge = array_intersect(
220 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() )
222 if ( !$rolesToMerge ) {
228 $rolesToMerge = array_intersect(
229 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
231 if ( !$rolesToMerge ) {
237 $diffRoles = array_intersect(
238 $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
240 foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) {
241 if ( $oldRev->hasSlot( $role ) ) {
242 $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) );
244 $newRev->removeSlot( $role );
247 $rolesToMerge = $diffRoles;
258 foreach ( $rolesToMerge as $role ) {
259 if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !
$curRev->
hasSlot( $role ) ) {
265 foreach ( $rolesToMerge as $role ) {
266 $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent();
267 $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent();
268 $curContent =
$curRev->
getSlot( $role, RevisionRecord::RAW )->getContent();
269 $newContent = $undoContent->getContentHandler()
270 ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest );
271 if ( !$newContent ) {
274 $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) );
280 private function generateDiffOrPreview() {
281 $newRev = $this->getNewRevision();
282 if ( $newRev->hasSameContent( $this->curRev ) ) {
287 $diffEngine->setRevisions( $this->curRev, $newRev );
289 $oldtitle = $this->context->msg(
'currentrev' )->parse();
290 $newtitle = $this->context->msg(
'yourtext' )->parse();
292 if ( $this->
getRequest()->getCheck(
'wpPreview' ) ) {
293 $this->showPreview( $newRev );
296 $diffText = $diffEngine->getDiff( $oldtitle, $newtitle );
297 $diffEngine->showDiffStyle();
298 return '<div id="wikiDiff">' . $diffText .
'</div>';
307 # provide a anchor link to the form
308 $continueEditing =
'<span class="mw-continue-editing">' .
309 '[[#mw-mcrundo-form|' .
310 $this->context->getLanguage()->getArrow() .
' ' .
311 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
313 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
315 $parserOptions = $this->
getWikiPage()->makeParserOptions( $this->context );
316 $parserOptions->setRenderReason(
'page-preview' );
317 $parserOptions->setIsPreview(
true );
318 $parserOptions->setIsSectionPreview(
false );
320 $parserOutput = $this->revisionRenderer
321 ->getRenderedRevision( $rev, $parserOptions, $this->
getAuthority() )
322 ->getRevisionParserOutput();
324 $previewHTML = $parserOutput->runOutputPipeline( $parserOptions, [
325 'enableSectionEditLinks' =>
false,
326 'includeDebugInfo' =>
true,
327 ] )->getContentHolderText();
329 $out->addParserOutputMetadata( $parserOutput );
330 if ( count( $parserOutput->getWarnings() ) ) {
331 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
334 $m = $this->context->msg(
335 'content-failed-to-parse',
338 $note .=
"\n\n" . $m->parse();
342 $previewhead = Html::rawElement(
343 'div', [
'class' =>
'previewnote' ],
345 'h2', [
'id' =>
'mw-previewheader' ],
346 $this->context->msg(
'preview' )->text()
349 $out->parseAsInterface( $note )
353 $out->addHTML( $previewhead . $previewHTML );
357 if ( !$this->
getRequest()->getCheck(
'wpSave' ) ) {
362 $updater = $this->
getWikiPage()->newPageUpdater( $this->context->getUser() );
363 $curRev = $updater->grabParentRevision();
369 return Status::newFatal(
'mcrundo-changed' );
374 if ( !$status->isOK() ) {
378 $newRev = $this->getNewRevision();
379 if ( !$newRev->hasSameContent(
$curRev ) ) {
381 foreach ( $newRev->getSlotRoles() as $slotRole ) {
382 $slot = $newRev->getSlot( $slotRole, RevisionRecord::RAW );
385 $hookResult = $hookRunner->onEditFilterMergedContent(
389 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ),
394 if ( !$hookResult ) {
395 if ( $status->isGood() ) {
396 $status->error(
'hookaborted' );
400 } elseif ( !$status->isOK() ) {
401 if ( !$status->getMessages() ) {
402 $status->error(
'hookaborted' );
411 foreach ( $newRev->getSlots()->getSlots() as $slot ) {
412 $updater->setSlot( $slot );
415 if ( !$newRev->hasSlot( $role ) ) {
416 $updater->removeSlot( $role );
420 $updater->markAsRevert( EditResult::REVERT_UNDO, $this->undo, $this->undoafter );
423 ->authorizeWrite(
'autopatrol', $this->
getTitle() )
425 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
428 $updater->saveRevision(
429 CommentStoreComment::newUnsavedComment(
430 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ) ),
434 return $updater->getStatus();
437 return Status::newGood();
450 'default' =>
function () {
451 return $this->generateDiffOrPreview();
457 'name' =>
'wpSummary',
458 'cssclass' =>
'mw-summary',
459 'label-message' =>
'summary',
460 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
461 'value' => $request->getVal(
'wpSummary',
'' ),
463 'spellcheck' =>
'true',
465 'summarypreview' => [
467 'label-message' =>
'summary-preview',
472 if ( $request->getCheck(
'wpSummary' ) ) {
473 $ret[
'summarypreview'][
'default'] = Html::rawElement(
475 [
'class' =>
'mw-summary-preview' ],
476 $this->commentFormatter->formatBlock(
477 trim( $request->getVal(
'wpSummary' ) ),
483 unset( $ret[
'summarypreview'] );
492 $labelAsPublish = $this->context->getConfig()->get( MainConfigNames::EditSubmitButtonLabelPublish );
494 $form->
setId(
'mw-mcrundo-form' );
497 $form->
setSubmitTextMsg( $labelAsPublish ?
'publishchanges' :
'savechanges' );
501 'name' =>
'wpPreview',
503 'label-message' =>
'showpreview',
504 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'preview' ),
509 'label-message' =>
'showdiff',
510 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'diff' ),
527 return '<div style="clear:both"></div>';
Legacy class representing an editable page and handling UI for some page actions.
An error page which can definitely be safely rendered using the OutputPage.
Exception representing a failure to serialize or unserialize a content object.