45 protected int $undo = 0;
46 protected int $undoafter = 0;
47 protected int $cur = 0;
56 private bool $useRCPatrol;
76 parent::__construct( $article, $context );
77 $this->readOnlyMode = $readOnlyMode;
78 $this->revisionLookup = $revisionLookup;
79 $this->revisionRenderer = $revisionRenderer;
80 $this->commentFormatter = $commentFormatter;
81 $this->useRCPatrol = $config->
get( MainConfigNames::UseRCPatrol );
100 MediaWiki\Session\SessionManager::getGlobalSession()->persist();
106 $out->setRobotPolicy(
'noindex,nofollow' );
110 if ( $this->readOnlyMode->isReadOnly() ) {
112 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
113 [
'readonlywarning', $this->readOnlyMode->getReason() ]
115 } elseif ( $this->context->getUser()->isAnon() ) {
118 if ( !$this->
getRequest()->getCheck(
'wpPreview' ) ) {
124 SpecialPage::getTitleFor(
'Userlogin' )->getFullURL( [
125 'returnto' => $this->getTitle()->getPrefixedDBkey()
128 SpecialPage::getTitleFor(
'CreateAccount' )->getFullURL( [
129 'returnto' => $this->getTitle()->getPrefixedDBkey()
132 'mw-anon-edit-warning'
138 $out->msg(
'anonpreviewwarning' )->parse(),
139 'mw-anon-preview-warning'
149 $this->undoafter = $this->
getRequest()->getInt(
'undoafter' );
150 $this->undo = $this->
getRequest()->getInt(
'undo' );
152 if ( $this->undo == 0 || $this->undoafter == 0 ) {
153 throw new ErrorPageError(
'mcrundofailed',
'mcrundo-missingparam' );
161 $this->cur = $this->
getRequest()->getInt(
'cur', $this->curRev->getId() );
165 parent::checkCanExecute( $user );
171 $undoRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undo );
172 $oldRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undoafter );
174 if ( $undoRev ===
null || $oldRev ===
null ||
175 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
176 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
187 private function getNewRevision() {
188 $undoRev = $this->revisionLookup->getRevisionById( $this->undo );
189 $oldRev = $this->revisionLookup->getRevisionById( $this->undoafter );
190 $curRev = $this->curRev;
192 $isLatest = $curRev->
getId() === $undoRev->getId();
194 if ( $undoRev ===
null || $oldRev ===
null ||
195 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
196 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
203 return MutableRevisionRecord::newFromParentRevision( $oldRev );
206 $newRev = MutableRevisionRecord::newFromParentRevision( $curRev );
210 $rolesToMerge = array_unique( array_merge(
211 $oldRev->getSlotRoles(),
212 $undoRev->getSlotRoles(),
218 $rolesToMerge = array_intersect(
219 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() )
221 if ( !$rolesToMerge ) {
227 $rolesToMerge = array_intersect(
228 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
230 if ( !$rolesToMerge ) {
236 $diffRoles = array_intersect(
237 $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
239 foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) {
240 if ( $oldRev->hasSlot( $role ) ) {
241 $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) );
243 $newRev->removeSlot( $role );
246 $rolesToMerge = $diffRoles;
257 foreach ( $rolesToMerge as $role ) {
258 if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !
$curRev->
hasSlot( $role ) ) {
264 foreach ( $rolesToMerge as $role ) {
265 $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent();
266 $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent();
267 $curContent =
$curRev->
getSlot( $role, RevisionRecord::RAW )->getContent();
268 $newContent = $undoContent->getContentHandler()
269 ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest );
270 if ( !$newContent ) {
273 $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) );
279 private function generateDiffOrPreview() {
280 $newRev = $this->getNewRevision();
281 if ( $newRev->hasSameContent( $this->curRev ) ) {
286 $diffEngine->setRevisions( $this->curRev, $newRev );
288 $oldtitle = $this->context->msg(
'currentrev' )->parse();
289 $newtitle = $this->context->msg(
'yourtext' )->parse();
291 if ( $this->
getRequest()->getCheck(
'wpPreview' ) ) {
292 $this->showPreview( $newRev );
295 $diffText = $diffEngine->getDiff( $oldtitle, $newtitle );
296 $diffEngine->showDiffStyle();
297 return '<div id="wikiDiff">' . $diffText .
'</div>';
306 # provide a anchor link to the form
307 $continueEditing =
'<span class="mw-continue-editing">' .
308 '[[#mw-mcrundo-form|' .
309 $this->context->getLanguage()->getArrow() .
' ' .
310 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
312 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
314 $parserOptions = $this->
getWikiPage()->makeParserOptions( $this->context );
315 $parserOptions->setRenderReason(
'page-preview' );
316 $parserOptions->setIsPreview(
true );
317 $parserOptions->setIsSectionPreview(
false );
319 $parserOutput = $this->revisionRenderer
320 ->getRenderedRevision( $rev, $parserOptions, $this->
getAuthority() )
321 ->getRevisionParserOutput();
322 $previewHTML = $parserOutput->getText( [
323 'enableSectionEditLinks' =>
false,
324 'includeDebugInfo' =>
true,
327 $out->addParserOutputMetadata( $parserOutput );
328 if ( count( $parserOutput->getWarnings() ) ) {
329 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
332 $m = $this->context->msg(
333 'content-failed-to-parse',
336 $note .=
"\n\n" . $m->parse();
340 $previewhead = Html::rawElement(
341 'div', [
'class' =>
'previewnote' ],
343 'h2', [
'id' =>
'mw-previewheader' ],
344 $this->context->msg(
'preview' )->text()
347 $out->parseAsInterface( $note )
351 $out->addHTML( $previewhead . $previewHTML );
355 if ( !$this->
getRequest()->getCheck(
'wpSave' ) ) {
360 $updater = $this->
getWikiPage()->newPageUpdater( $this->context->getUser() );
361 $curRev = $updater->grabParentRevision();
367 return Status::newFatal(
'mcrundo-changed' );
372 if ( !$status->isOK() ) {
376 $newRev = $this->getNewRevision();
377 if ( !$newRev->hasSameContent(
$curRev ) ) {
379 foreach ( $newRev->getSlotRoles() as $slotRole ) {
380 $slot = $newRev->getSlot( $slotRole, RevisionRecord::RAW );
383 $hookResult = $hookRunner->onEditFilterMergedContent(
387 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ),
392 if ( !$hookResult ) {
393 if ( $status->isGood() ) {
394 $status->error(
'hookaborted' );
398 } elseif ( !$status->isOK() ) {
399 if ( !$status->getErrors() ) {
400 $status->error(
'hookaborted' );
409 foreach ( $newRev->getSlots()->getSlots() as $slot ) {
410 $updater->setSlot( $slot );
413 if ( !$newRev->hasSlot( $role ) ) {
414 $updater->removeSlot( $role );
418 $updater->markAsRevert( EditResult::REVERT_UNDO, $this->undo, $this->undoafter );
421 ->authorizeWrite(
'autopatrol', $this->
getTitle() )
423 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
426 $updater->saveRevision(
427 CommentStoreComment::newUnsavedComment(
428 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ) ),
432 return $updater->getStatus();
435 return Status::newGood();
448 'default' =>
function () {
449 return $this->generateDiffOrPreview();
455 'name' =>
'wpSummary',
456 'cssclass' =>
'mw-summary',
457 'label-message' =>
'summary',
458 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
459 'value' => $request->getVal(
'wpSummary',
'' ),
461 'spellcheck' =>
'true',
463 'summarypreview' => [
465 'label-message' =>
'summary-preview',
470 if ( $request->getCheck(
'wpSummary' ) ) {
471 $ret[
'summarypreview'][
'default'] = Html::rawElement(
473 [
'class' =>
'mw-summary-preview' ],
474 $this->commentFormatter->formatBlock(
475 trim( $request->getVal(
'wpSummary' ) ),
481 unset( $ret[
'summarypreview'] );
488 $form->setWrapperLegendMsg(
'confirm-mcrundo-title' );
490 $labelAsPublish = $this->context->getConfig()->get( MainConfigNames::EditSubmitButtonLabelPublish );
492 $form->setId(
'mw-mcrundo-form' );
493 $form->setSubmitName(
'wpSave' );
494 $form->setSubmitTooltip( $labelAsPublish ?
'publish' :
'save' );
495 $form->setSubmitTextMsg( $labelAsPublish ?
'publishchanges' :
'savechanges' );
496 $form->showCancel(
true );
497 $form->setCancelTarget( $this->
getTitle() );
499 'name' =>
'wpPreview',
501 'label-message' =>
'showpreview',
502 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'preview' ),
507 'label-message' =>
'showdiff',
508 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'diff' ),
515 $form->addHiddenField(
'undo', $this->undo );
516 $form->addHiddenField(
'undoafter', $this->undoafter );
517 $form->addHiddenField(
'cur', $this->curRev->getId() );
525 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.