Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
30 / 36
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractUndoAction
83.33% covered (warning)
83.33%
30 / 36
50.00% covered (danger)
50.00%
4 / 8
12.67
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onView
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 needsReadRights
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRestriction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDiffFromRequest
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 tryPatching
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 showUndoErrorPage
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types = 1 );
4
5namespace EntitySchema\MediaWiki\Actions;
6
7use Diff\DiffOp\Diff\Diff;
8use DomainException;
9use EntitySchema\MediaWiki\Content\EntitySchemaContent;
10use EntitySchema\MediaWiki\UndoHandler;
11use MediaWiki\Actions\FormlessAction;
12use MediaWiki\Context\IContextSource;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Page\Article;
15use MediaWiki\Request\WebRequest;
16use MediaWiki\Revision\RevisionStore;
17use MediaWiki\Revision\SlotRecord;
18use MediaWiki\Status\Status;
19
20/**
21 * @license GPL-2.0-or-later
22 */
23abstract class AbstractUndoAction extends FormlessAction {
24
25    protected RevisionStore $revisionStore;
26
27    public function __construct(
28        Article $article,
29        IContextSource $context,
30        RevisionStore $revisionStore
31    ) {
32        parent::__construct( $article, $context );
33        $this->revisionStore = $revisionStore;
34    }
35
36    /** @inheritDoc */
37    public function getName() {
38        return 'view';
39    }
40
41    /** @inheritDoc */
42    public function onView() {
43        return null;
44    }
45
46    /** @inheritDoc */
47    public function needsReadRights() {
48        // Pages in $wgWhitelistRead can be viewed without having the 'read'
49        // right. We rely on Article::view() to properly check read access.
50        return false;
51    }
52
53    public function getRestriction(): string {
54        return $this->getName();
55    }
56
57    /** @return Status<Diff> */
58    protected function getDiffFromRequest( WebRequest $req ): Status {
59        $newerRevision = $this->revisionStore->getRevisionById( $req->getInt( 'undo' ) );
60        $olderRevision = $this->revisionStore->getRevisionById( $req->getInt( 'undoafter' ) );
61
62        if ( $newerRevision === null || $olderRevision === null ) {
63            return Status::newFatal( 'entityschema-undo-bad-revisions' );
64        }
65
66        /** @var EntitySchemaContent $undoFromContent */
67        $undoFromContent = $newerRevision->getContent( SlotRecord::MAIN );
68        '@phan-var EntitySchemaContent $undoFromContent';
69        /** @var EntitySchemaContent $undoToContent */
70        $undoToContent = $olderRevision->getContent( SlotRecord::MAIN );
71        '@phan-var EntitySchemaContent $undoToContent';
72        $undoHandler = new UndoHandler();
73        try {
74            $undoHandler->validateContentIds( $undoFromContent, $undoToContent );
75        } catch ( DomainException ) {
76            return Status::newFatal( 'entityschema-error-inconsistent-id' );
77        }
78
79        return $undoHandler->getDiffFromContents( $undoFromContent, $undoToContent );
80    }
81
82    /**
83     * Try applying the diff to the latest revision of this page.
84     *
85     * @param Diff $diff
86     *
87     * @return Status contains array of the patched schema data and the revision that was patched
88     * @phan-return Status<array{0:\EntitySchema\Services\Converter\FullArrayEntitySchemaData,1:int}>
89     */
90    protected function tryPatching( Diff $diff ): Status {
91        $revStore = MediaWikiServices::getInstance()->getRevisionStore();
92        $baseRevId = $this->getTitle()->getLatestRevID();
93        /** @var EntitySchemaContent $baseContent */
94        $baseContent = $revStore
95            ->getRevisionById( $baseRevId )
96            ->getContent( SlotRecord::MAIN );
97        '@phan-var EntitySchemaContent $baseContent';
98        $undoHandler = new UndoHandler();
99        $status = $undoHandler->tryPatching( $diff, $baseContent );
100        if ( $status->isGood() ) {
101            return Status::newGood( [ $status->getValue(), $baseRevId ] );
102        }
103
104        // @phan-suppress-next-line PhanTypeMismatchReturn -- error status, different type parameter is irrelevant
105        return $status;
106    }
107
108    /**
109     * Output an error page showing the given status
110     *
111     * @param Status $status The status to report.
112     */
113    protected function showUndoErrorPage( Status $status ): void {
114        $this->getOutput()->prepareErrorPage();
115        $this->getOutput()->setPageTitleMsg(
116            $this->msg( 'entityschema-undo-heading-failed' )
117        );
118        $this->getOutput()->setHTMLTitle(
119            $this->msg( 'errorpagetitle' )
120        );
121
122        $this->getOutput()->addHTML( $status->getMessage()->parse() );
123
124        $this->getOutput()->returnToMain();
125    }
126
127}