66 private readonly
bool $useRCPatrol;
80 parent::__construct( $article,
$context );
110 $out->setRobotPolicy(
'noindex,nofollow' );
114 if ( $this->readOnlyMode->isReadOnly() ) {
116 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
117 [
'readonlywarning', $this->readOnlyMode->getReason() ]
119 } elseif ( $this->context->getUser()->isAnon() ) {
120 $shouldAutoCreateTempUser = $this->tempUserCreator->shouldAutoCreate(
123 if ( !$this->
getRequest()->getCheck(
'wpPreview' ) ) {
127 $shouldAutoCreateTempUser ?
'autocreate-edit-warning' :
'anoneditwarning',
130 'returnto' => $this->
getTitle()->getPrefixedDBkey()
134 'returnto' => $this->
getTitle()->getPrefixedDBkey()
137 'mw-anon-edit-warning'
144 $shouldAutoCreateTempUser ?
'autocreate-preview-warning' :
'anonpreviewwarning'
146 'mw-anon-preview-warning'
156 $this->undoafter = $this->
getRequest()->getInt(
'undoafter' );
157 $this->undo = $this->
getRequest()->getInt(
'undo' );
159 if ( $this->undo == 0 || $this->undoafter == 0 ) {
160 throw new ErrorPageError(
'mcrundofailed',
'mcrundo-missingparam' );
168 $this->cur = $this->
getRequest()->getInt(
'cur', $this->curRev->getId() );
173 parent::checkCanExecute( $user );
179 $undoRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undo );
180 $oldRev = $this->revisionLookup->getRevisionByTitle( $title, $this->undoafter );
182 if ( $undoRev ===
null || $oldRev ===
null ||
183 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
184 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
195 private function getNewRevision() {
196 $undoRev = $this->revisionLookup->getRevisionById( $this->undo );
197 $oldRev = $this->revisionLookup->getRevisionById( $this->undoafter );
202 if ( $undoRev ===
null || $oldRev ===
null ||
203 $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
204 $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
218 $rolesToMerge = array_unique( array_merge(
219 $oldRev->getSlotRoles(),
220 $undoRev->getSlotRoles(),
226 $rolesToMerge = array_intersect(
227 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() )
229 if ( !$rolesToMerge ) {
230 throw new ErrorPageError(
'mcrundofailed',
'undo-nochange' );
235 $rolesToMerge = array_intersect(
236 $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
238 if ( !$rolesToMerge ) {
239 throw new ErrorPageError(
'mcrundofailed',
'undo-nochange' );
244 $diffRoles = array_intersect(
245 $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent(
$curRev->
getSlots() )
247 foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) {
248 if ( $oldRev->hasSlot( $role ) ) {
249 $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) );
251 $newRev->removeSlot( $role );
254 $rolesToMerge = $diffRoles;
265 foreach ( $rolesToMerge as $role ) {
266 if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !
$curRev->
hasSlot( $role ) ) {
267 throw new ErrorPageError(
'mcrundofailed',
'undo-failure' );
272 foreach ( $rolesToMerge as $role ) {
273 $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent();
274 $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent();
275 $curContent =
$curRev->
getSlot( $role, RevisionRecord::RAW )->getContent();
276 $newContent = $undoContent->getContentHandler()
277 ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest );
278 if ( !$newContent ) {
279 throw new ErrorPageError(
'mcrundofailed',
'undo-failure' );
281 $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) );
287 private function generateDiffOrPreview(): string {
288 $newRev = $this->getNewRevision();
289 if ( $newRev->hasSameContent( $this->curRev ) ) {
290 throw new ErrorPageError(
'mcrundofailed',
'undo-nochange' );
293 $diffEngine =
new DifferenceEngine( $this->context );
294 $diffEngine->setRevisions( $this->curRev, $newRev );
296 $oldtitle = $this->context->msg(
'currentrev' )->parse();
297 $newtitle = $this->context->msg(
'yourtext' )->parse();
299 if ( $this->
getRequest()->getCheck(
'wpPreview' ) ) {
300 $this->showPreview( $newRev );
303 $diffText = $diffEngine->getDiff( $oldtitle, $newtitle );
304 $diffEngine->showDiffStyle();
305 return '<div id="wikiDiff">' . $diffText .
'</div>';
309 private function showPreview( RevisionRecord $rev ) {
314 $previewIssuesHtml =
'';
317 # provide a anchor link to the form
318 $continueEditingHtml = Html::rawElement(
320 [
'class' =>
'mw-continue-editing' ],
321 $this->linkRenderer->makePreloadedLink(
322 new TitleValue(
NS_MAIN,
'',
'mw-mcrundo-form' ),
323 $this->context->getLanguage()->getArrow() .
' ' . $this->context->msg(
'continue-editing' )->text()
327 $noteHtml .= Html::noticeBox(
328 $this->context->msg(
'previewnote' )->parse() .
' ' . $continueEditingHtml
331 $parserOptions = $this->getWikiPage()->makeParserOptions( $this->context );
332 $parserOptions->setRenderReason(
'page-preview' );
333 $parserOptions->setIsPreview(
true );
334 $parserOptions->setIsSectionPreview(
false );
335 $parserOptions->setSuppressSectionEditLinks();
337 $parserOutput = $this->revisionRenderer
338 ->getRenderedRevision( $rev, $parserOptions, $this->
getAuthority() )
339 ->getRevisionParserOutput();
341 $previewHTML = $parserOutput->runOutputPipeline( $parserOptions, [
342 'includeDebugInfo' =>
true,
343 ] )->getContentHolderText();
345 $out->addParserOutputMetadata( $parserOutput );
346 $parserWarnings = $parserOutput->getWarningMsgs();
347 if ( $parserWarnings ) {
349 foreach ( $parserWarnings as $mv ) {
350 $warningsHtml .= $this->context->msg( $mv )->parse() .
"<br>";
352 $previewIssuesHtml .= Html::warningBox( $warningsHtml );
354 }
catch ( MWContentSerializationException $ex ) {
355 $m = $this->context->msg(
356 'content-failed-to-parse',
359 $previewIssuesHtml .= Html::errorBox( $m->parse() );
363 $previewhead = Html::rawElement(
364 'div', [
'class' =>
'previewnote' ],
366 'h2', [
'id' =>
'mw-previewheader' ],
367 $this->context->msg(
'preview' )->text()
368 ) . $previewIssuesHtml . $noteHtml
371 $out->addHTML( $previewhead . $previewHTML );
376 if ( !$this->getRequest()->getCheck(
'wpSave' ) ) {
381 $user = $this->context->getUser();
382 $request = $this->context->getRequest();
384 if ( $this->tempUserCreator->shouldAutoCreate( $user,
'edit' ) ) {
385 $status = $this->tempUserCreator->create(
null, $request );
386 if ( !$status->isOK() ) {
389 $user = $status->getUser();
390 $this->authManager->setRequestContextUserFromSessionUser();
393 $updater = $this->getWikiPage()->newPageUpdater( $user );
394 $curRev = $updater->grabParentRevision();
399 if ( $this->cur !== $curRev->getId() ) {
400 return Status::newFatal(
'mcrundo-changed' );
404 $this->getAuthority()->authorizeWrite(
'edit', $this->getTitle(), $status );
405 if ( !$status->isOK() ) {
409 $newRev = $this->getNewRevision();
410 if ( !$newRev->hasSameContent( $curRev ) ) {
411 $hookRunner = $this->getHookRunner();
412 foreach ( $newRev->getSlotRoles() as $slotRole ) {
413 $slot = $newRev->getSlot( $slotRole, RevisionRecord::RAW );
416 $hookResult = $hookRunner->onEditFilterMergedContent(
420 trim( $this->getRequest()->getVal(
'wpSummary' ) ??
'' ),
425 if ( !$hookResult ) {
426 if ( $status->isGood() ) {
427 $status->error(
'hookaborted' );
431 } elseif ( !$status->isOK() ) {
432 if ( !$status->getMessages() ) {
433 $status->error(
'hookaborted' );
442 foreach ( $newRev->getSlots()->getSlots() as $slot ) {
443 $updater->setSlot( $slot );
445 foreach ( $curRev->getSlotRoles() as $role ) {
446 if ( !$newRev->hasSlot( $role ) ) {
447 $updater->removeSlot( $role );
451 $updater->setCause( PageUpdateCauses::CAUSE_UNDO );
454 if ( $this->useRCPatrol && $this->getAuthority()
455 ->authorizeWrite(
'autopatrol', $this->getTitle() )
457 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
460 $updater->saveRevision(
462 trim( $this->getRequest()->getVal(
'wpSummary' ) ??
'' ) ),
466 return $updater->getStatus();
469 return Status::newGood();
479 $request = $this->getRequest();
484 'default' => $this->generateDiffOrPreview( ... )
489 'name' =>
'wpSummary',
490 'cssclass' =>
'mw-summary',
491 'label-message' =>
'summary',
492 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
493 'value' => $request->getVal(
'wpSummary',
'' ),
495 'spellcheck' =>
'true',
497 'summarypreview' => [
499 'label-message' =>
'summary-preview',
504 if ( $request->getCheck(
'wpSummary' ) ) {
505 $ret[
'summarypreview'][
'default'] = Html::rawElement(
507 [
'class' =>
'mw-summary-preview' ],
508 $this->commentFormatter->formatBlock(
509 trim( $request->getVal(
'wpSummary' ) ),
515 unset( $ret[
'summarypreview'] );
526 $form->
setId(
'mw-mcrundo-form' );
529 $form->
setSubmitTextMsg( $labelAsPublish ?
'publishchanges' :
'savechanges' );
533 'name' =>
'wpPreview',
535 'label-message' =>
'showpreview',
536 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'preview' ),
541 'label-message' =>
'showdiff',
542 'attribs' => Linker::tooltipAndAccesskeyAttribs(
'diff' ),
545 $this->addStatePropagationFields( $form );
556 $this->getOutput()->redirect( $this->getTitle()->getFullURL() );
561 return '<div style="clear:both"></div>';
566class_alias( McrUndoAction::class,
'McrUndoAction' );
const EDIT_UPDATE
Article is assumed to be pre-existing, fail if it doesn't exist.
const EDIT_AUTOSUMMARY
Fill in blank summaries with generated text where possible.
An error page which can definitely be safely rendered using the OutputPage.
Exception representing a failure to serialize or unserialize a content object.
A class containing constants representing the names of configuration variables.
const UseRCPatrol
Name constant for the UseRCPatrol setting, for use with Config::get()
const EditSubmitButtonLabelPublish
Name constant for the EditSubmitButtonLabelPublish setting, for use with Config::get()
Legacy class representing an editable page and handling UI for some page actions.
Parent class for all special pages.
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,...
Interface for objects which can provide a MediaWiki context on request.
Constants for representing well known causes for page updates.