54 private bool $useRCPatrol;
74 parent::__construct( $article, $context );
75 $this->readOnlyMode = $readOnlyMode;
76 $this->revisionLookup = $revisionLookup;
77 $this->revisionRenderer = $revisionRenderer;
78 $this->commentFormatter = $commentFormatter;
79 $this->useRCPatrol = $config->
get( MainConfigNames::UseRCPatrol );
98 MediaWiki\Session\SessionManager::getGlobalSession()->persist();
104 $out->setRobotPolicy(
'noindex,nofollow' );
108 if ( $this->readOnlyMode->isReadOnly() ) {
110 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
111 [
'readonlywarning', $this->readOnlyMode->getReason() ]
113 } elseif ( $this->context->getUser()->isAnon() ) {
116 if ( !$this->
getRequest()->getCheck(
'wpPreview' ) ) {
122 SpecialPage::getTitleFor(
'Userlogin' )->getFullURL( [
123 'returnto' => $this->getTitle()->getPrefixedDBkey()
126 SpecialPage::getTitleFor(
'CreateAccount' )->getFullURL( [
127 'returnto' => $this->getTitle()->getPrefixedDBkey()
130 'mw-anon-edit-warning'
136 $out->msg(
'anonpreviewwarning' )->parse(),
137 'mw-anon-preview-warning'
147 $this->undoafter = $this->
getRequest()->getInt(
'undoafter' );
148 $this->undo = $this->
getRequest()->getInt(
'undo' );
150 if ( $this->undo == 0 || $this->undoafter == 0 ) {
151 throw new ErrorPageError(
'mcrundofailed',
'mcrundo-missingparam' );
159 $this->cur = $this->
getRequest()->getInt(
'cur', $this->curRev->getId() );
163 parent::checkCanExecute( $user );
169 $undoRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undo );
170 $oldRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undoafter );
172 if ( $undoRev ===
null || $oldRev ===
null ||
173 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
174 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
185 private function getNewRevision() {
186 $undoRev = $this->revisionLookup->getRevisionById( $this->undo );
187 $oldRev = $this->revisionLookup->getRevisionById( $this->undoafter );
188 $curRev = $this->curRev;
190 $isLatest = $curRev->
getId() === $undoRev->getId();
192 if ( $undoRev ===
null || $oldRev ===
null ||
193 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
194 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
201 return MutableRevisionRecord::newFromParentRevision( $oldRev );
204 $newRev = MutableRevisionRecord::newFromParentRevision( $curRev );
208 $rolesToMerge = array_unique( array_merge(
209 $oldRev->getSlotRoles(),
210 $undoRev->getSlotRoles(),
216 $rolesToMerge = array_intersect(
217 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() )
219 if ( !$rolesToMerge ) {
225 $rolesToMerge = array_intersect(
226 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
228 if ( !$rolesToMerge ) {
234 $diffRoles = array_intersect(
235 $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
237 foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) {
238 if ( $oldRev->hasSlot( $role ) ) {
239 $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) );
241 $newRev->removeSlot( $role );
244 $rolesToMerge = $diffRoles;
255 foreach ( $rolesToMerge as $role ) {
256 if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !
$curRev->
hasSlot( $role ) ) {
262 foreach ( $rolesToMerge as $role ) {
263 $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent();
264 $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent();
265 $curContent =
$curRev->
getSlot( $role, RevisionRecord::RAW )->getContent();
266 $newContent = $undoContent->getContentHandler()
267 ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest );
268 if ( !$newContent ) {
271 $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) );
277 private function generateDiffOrPreview() {
278 $newRev = $this->getNewRevision();
279 if ( $newRev->hasSameContent( $this->curRev ) ) {
284 $diffEngine->setRevisions( $this->curRev, $newRev );
286 $oldtitle = $this->context->msg(
'currentrev' )->parse();
287 $newtitle = $this->context->msg(
'yourtext' )->parse();
289 if ( $this->
getRequest()->getCheck(
'wpPreview' ) ) {
290 $this->showPreview( $newRev );
293 $diffText = $diffEngine->getDiff( $oldtitle, $newtitle );
294 $diffEngine->showDiffStyle();
295 return '<div id="wikiDiff">' . $diffText .
'</div>';
304 # provide a anchor link to the form
305 $continueEditing =
'<span class="mw-continue-editing">' .
306 '[[#mw-mcrundo-form|' .
307 $this->context->getLanguage()->getArrow() .
' ' .
308 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
310 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
312 $parserOptions = $this->
getWikiPage()->makeParserOptions( $this->context );
313 $parserOptions->setRenderReason(
'page-preview' );
314 $parserOptions->setIsPreview(
true );
315 $parserOptions->setIsSectionPreview(
false );
317 $parserOutput = $this->revisionRenderer
318 ->getRenderedRevision( $rev, $parserOptions, $this->
getAuthority() )
319 ->getRevisionParserOutput();
320 $previewHTML = $parserOutput->getText( [
321 'enableSectionEditLinks' =>
false,
322 'includeDebugInfo' =>
true,
325 $out->addParserOutputMetadata( $parserOutput );
326 if ( count( $parserOutput->getWarnings() ) ) {
327 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
330 $m = $this->context->msg(
331 'content-failed-to-parse',
334 $note .=
"\n\n" . $m->parse();
338 $previewhead = Html::rawElement(
339 'div', [
'class' =>
'previewnote' ],
341 'h2', [
'id' =>
'mw-previewheader' ],
342 $this->context->msg(
'preview' )->text()
345 $out->parseAsInterface( $note )
349 $out->addHTML( $previewhead . $previewHTML );
353 if ( !$this->
getRequest()->getCheck(
'wpSave' ) ) {
358 $updater = $this->
getWikiPage()->newPageUpdater( $this->context->getUser() );
359 $curRev = $updater->grabParentRevision();
365 return Status::newFatal(
'mcrundo-changed' );
370 if ( !$status->isOK() ) {
374 $newRev = $this->getNewRevision();
375 if ( !$newRev->hasSameContent(
$curRev ) ) {
377 foreach ( $newRev->getSlotRoles() as $slotRole ) {
378 $slot = $newRev->getSlot( $slotRole, RevisionRecord::RAW );
381 $hookResult = $hookRunner->onEditFilterMergedContent(
385 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ),
390 if ( !$hookResult ) {
391 if ( $status->isGood() ) {
392 $status->error(
'hookaborted' );
396 } elseif ( !$status->isOK() ) {
397 if ( !$status->getErrors() ) {
398 $status->error(
'hookaborted' );
407 foreach ( $newRev->getSlots()->getSlots() as $slot ) {
408 $updater->setSlot( $slot );
411 if ( !$newRev->hasSlot( $role ) ) {
412 $updater->removeSlot( $role );
416 $updater->markAsRevert( EditResult::REVERT_UNDO, $this->undo, $this->undoafter );
419 ->authorizeWrite(
'autopatrol', $this->
getTitle() )
421 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
424 $updater->saveRevision(
425 CommentStoreComment::newUnsavedComment(
426 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ) ),
430 return $updater->getStatus();
433 return Status::newGood();
446 'default' =>
function () {
447 return $this->generateDiffOrPreview();
453 'name' =>
'wpSummary',
454 'cssclass' =>
'mw-summary',
455 'label-message' =>
'summary',
456 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
457 'value' => $request->getVal(
'wpSummary',
'' ),
459 'spellcheck' =>
'true',
461 'summarypreview' => [
463 'label-message' =>
'summary-preview',
468 if ( $request->getCheck(
'wpSummary' ) ) {
469 $ret[
'summarypreview'][
'default'] = Html::rawElement(
471 [
'class' =>
'mw-summary-preview' ],
472 $this->commentFormatter->formatBlock(
473 trim( $request->getVal(
'wpSummary' ) ),
479 unset( $ret[
'summarypreview'] );
486 $form->setWrapperLegendMsg(
'confirm-mcrundo-title' );
488 $labelAsPublish = $this->context->getConfig()->get( MainConfigNames::EditSubmitButtonLabelPublish );
490 $form->setId(
'mw-mcrundo-form' );
491 $form->setSubmitName(
'wpSave' );
492 $form->setSubmitTooltip( $labelAsPublish ?
'publish' :
'save' );
493 $form->setSubmitTextMsg( $labelAsPublish ?
'publishchanges' :
'savechanges' );
494 $form->showCancel(
true );
495 $form->setCancelTarget( $this->
getTitle() );
497 'name' =>
'wpPreview',
499 'label-message' =>
'showpreview',
500 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'preview' ),
505 'label-message' =>
'showdiff',
506 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'diff' ),
513 $form->addHiddenField(
'undo', $this->undo );
514 $form->addHiddenField(
'undoafter', $this->undoafter );
515 $form->addHiddenField(
'cur', $this->curRev->getId() );
523 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.