53 private bool $useRCPatrol;
73 parent::__construct( $article, $context );
74 $this->readOnlyMode = $readOnlyMode;
75 $this->revisionLookup = $revisionLookup;
76 $this->revisionRenderer = $revisionRenderer;
77 $this->commentFormatter = $commentFormatter;
78 $this->useRCPatrol = $config->
get( MainConfigNames::UseRCPatrol );
97 MediaWiki\Session\SessionManager::getGlobalSession()->persist();
103 $out->setRobotPolicy(
'noindex,nofollow' );
104 if ( $this->
getContext()->getConfig()->
get( MainConfigNames::UseMediaWikiUIEverywhere ) ) {
105 $out->addModuleStyles( [
106 'mediawiki.ui.input',
107 'mediawiki.ui.checkbox',
113 if ( $this->readOnlyMode->isReadOnly() ) {
115 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
116 [
'readonlywarning', $this->readOnlyMode->getReason() ]
118 } elseif ( $this->context->getUser()->isAnon() ) {
121 if ( !$this->
getRequest()->getCheck(
'wpPreview' ) ) {
127 SpecialPage::getTitleFor(
'Userlogin' )->getFullURL( [
128 'returnto' => $this->getTitle()->getPrefixedDBkey()
131 SpecialPage::getTitleFor(
'CreateAccount' )->getFullURL( [
132 'returnto' => $this->getTitle()->getPrefixedDBkey()
135 'mw-anon-edit-warning'
141 $out->msg(
'anonpreviewwarning' )->parse(),
142 'mw-anon-preview-warning'
152 $this->undoafter = $this->
getRequest()->getInt(
'undoafter' );
153 $this->undo = $this->
getRequest()->getInt(
'undo' );
155 if ( $this->undo == 0 || $this->undoafter == 0 ) {
156 throw new ErrorPageError(
'mcrundofailed',
'mcrundo-missingparam' );
164 $this->cur = $this->
getRequest()->getInt(
'cur', $this->curRev->getId() );
168 parent::checkCanExecute( $user );
174 $undoRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undo );
175 $oldRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undoafter );
177 if ( $undoRev ===
null || $oldRev ===
null ||
178 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
179 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
190 private function getNewRevision() {
191 $undoRev = $this->revisionLookup->getRevisionById( $this->undo );
192 $oldRev = $this->revisionLookup->getRevisionById( $this->undoafter );
193 $curRev = $this->curRev;
195 $isLatest = $curRev->
getId() === $undoRev->getId();
197 if ( $undoRev ===
null || $oldRev ===
null ||
198 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
199 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
206 return MutableRevisionRecord::newFromParentRevision( $oldRev );
209 $newRev = MutableRevisionRecord::newFromParentRevision( $curRev );
213 $rolesToMerge = array_unique( array_merge(
214 $oldRev->getSlotRoles(),
215 $undoRev->getSlotRoles(),
221 $rolesToMerge = array_intersect(
222 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() )
224 if ( !$rolesToMerge ) {
230 $rolesToMerge = array_intersect(
231 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
233 if ( !$rolesToMerge ) {
239 $diffRoles = array_intersect(
240 $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
242 foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) {
243 if ( $oldRev->hasSlot( $role ) ) {
244 $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) );
246 $newRev->removeSlot( $role );
249 $rolesToMerge = $diffRoles;
260 foreach ( $rolesToMerge as $role ) {
261 if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !
$curRev->
hasSlot( $role ) ) {
267 foreach ( $rolesToMerge as $role ) {
268 $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent();
269 $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent();
270 $curContent =
$curRev->
getSlot( $role, RevisionRecord::RAW )->getContent();
271 $newContent = $undoContent->getContentHandler()
272 ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest );
273 if ( !$newContent ) {
276 $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) );
282 private function generateDiffOrPreview() {
283 $newRev = $this->getNewRevision();
284 if ( $newRev->hasSameContent( $this->curRev ) ) {
289 $diffEngine->setRevisions( $this->curRev, $newRev );
291 $oldtitle = $this->context->msg(
'currentrev' )->parse();
292 $newtitle = $this->context->msg(
'yourtext' )->parse();
294 if ( $this->
getRequest()->getCheck(
'wpPreview' ) ) {
295 $this->showPreview( $newRev );
298 $diffText = $diffEngine->getDiff( $oldtitle, $newtitle );
299 $diffEngine->showDiffStyle();
300 return '<div id="wikiDiff">' . $diffText .
'</div>';
309 # provide a anchor link to the form
310 $continueEditing =
'<span class="mw-continue-editing">' .
311 '[[#mw-mcrundo-form|' .
312 $this->context->getLanguage()->getArrow() .
' ' .
313 $this->context->msg(
'continue-editing' )->text() .
']]</span>';
315 $note = $this->context->msg(
'previewnote' )->plain() .
' ' . $continueEditing;
317 $parserOptions = $this->
getWikiPage()->makeParserOptions( $this->context );
318 $parserOptions->setRenderReason(
'page-preview' );
319 $parserOptions->setIsPreview(
true );
320 $parserOptions->setIsSectionPreview(
false );
322 $parserOutput = $this->revisionRenderer
323 ->getRenderedRevision( $rev, $parserOptions, $this->
getAuthority() )
324 ->getRevisionParserOutput();
325 $previewHTML = $parserOutput->getText( [
326 'enableSectionEditLinks' =>
false,
327 'includeDebugInfo' =>
true,
330 $out->addParserOutputMetadata( $parserOutput );
331 if ( count( $parserOutput->getWarnings() ) ) {
332 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
335 $m = $this->context->msg(
336 'content-failed-to-parse',
339 $note .=
"\n\n" . $m->parse();
343 $previewhead = Html::rawElement(
344 'div', [
'class' =>
'previewnote' ],
346 'h2', [
'id' =>
'mw-previewheader' ],
347 $this->context->msg(
'preview' )->text()
350 $out->parseAsInterface( $note )
354 $pageViewLang = $this->
getTitle()->getPageViewLanguage();
355 $attribs = [
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
356 'class' =>
'mw-content-' . $pageViewLang->getDir() ];
357 $previewHTML = Html::rawElement(
'div', $attribs, $previewHTML );
359 $out->addHTML( $previewhead . $previewHTML );
363 if ( !$this->
getRequest()->getCheck(
'wpSave' ) ) {
368 $updater = $this->
getWikiPage()->newPageUpdater( $this->context->getUser() );
369 $curRev = $updater->grabParentRevision();
375 return Status::newFatal(
'mcrundo-changed' );
380 if ( !$status->isOK() ) {
384 $newRev = $this->getNewRevision();
385 if ( !$newRev->hasSameContent(
$curRev ) ) {
387 foreach ( $newRev->getSlotRoles() as $slotRole ) {
388 $slot = $newRev->getSlot( $slotRole, RevisionRecord::RAW );
391 $hookResult = $hookRunner->onEditFilterMergedContent(
395 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ),
400 if ( !$hookResult ) {
401 if ( $status->isGood() ) {
402 $status->error(
'hookaborted' );
406 } elseif ( !$status->isOK() ) {
407 if ( !$status->getErrors() ) {
408 $status->error(
'hookaborted' );
417 foreach ( $newRev->getSlots()->getSlots() as $slot ) {
418 $updater->setSlot( $slot );
421 if ( !$newRev->hasSlot( $role ) ) {
422 $updater->removeSlot( $role );
426 $updater->markAsRevert( EditResult::REVERT_UNDO, $this->undo, $this->undoafter );
429 ->authorizeWrite(
'autopatrol', $this->
getTitle() )
431 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
434 $updater->saveRevision(
435 CommentStoreComment::newUnsavedComment(
436 trim( $this->
getRequest()->getVal(
'wpSummary' ) ??
'' ) ),
440 return $updater->getStatus();
443 return Status::newGood();
456 'default' =>
function () {
457 return $this->generateDiffOrPreview();
463 'name' =>
'wpSummary',
464 'cssclass' =>
'mw-summary',
465 'label-message' =>
'summary',
466 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
467 'value' => $request->getVal(
'wpSummary',
'' ),
469 'spellcheck' =>
'true',
471 'summarypreview' => [
473 'label-message' =>
'summary-preview',
478 if ( $request->getCheck(
'wpSummary' ) ) {
479 $ret[
'summarypreview'][
'default'] = Xml::tags(
481 [
'class' =>
'mw-summary-preview' ],
482 $this->commentFormatter->formatBlock(
483 trim( $request->getVal(
'wpSummary' ) ),
489 unset( $ret[
'summarypreview'] );
498 $labelAsPublish = $this->context->getConfig()->get( MainConfigNames::EditSubmitButtonLabelPublish );
500 $form->
setId(
'mw-mcrundo-form' );
503 $form->
setSubmitTextMsg( $labelAsPublish ?
'publishchanges' :
'savechanges' );
507 'name' =>
'wpPreview',
509 'label-message' =>
'showpreview',
510 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'preview' ),
515 'label-message' =>
'showdiff',
516 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'diff' ),
533 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.
Interface for objects which can provide a MediaWiki context on request.