Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.02% covered (warning)
61.02%
36 / 59
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiDiscussionToolsCompare
61.02% covered (warning)
61.02%
36 / 59
71.43% covered (warning)
71.43%
5 / 7
47.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 execute
47.62% covered (danger)
47.62%
20 / 42
0.00% covered (danger)
0.00%
0 / 1
47.34
 addResult
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isInternal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isWriteMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\DiscussionTools;
4
5use ApiBase;
6use ApiMain;
7use MediaWiki\Extension\DiscussionTools\Hooks\HookUtils;
8use MediaWiki\Extension\VisualEditor\VisualEditorParsoidClientFactory;
9use MediaWiki\Revision\RevisionLookup;
10use MediaWiki\Revision\RevisionRecord;
11use Title;
12use Wikimedia\ParamValidator\ParamValidator;
13
14class ApiDiscussionToolsCompare extends ApiBase {
15
16    private CommentParser $commentParser;
17    private VisualEditorParsoidClientFactory $parsoidClientFactory;
18    private RevisionLookup $revisionLookup;
19
20    public function __construct(
21        ApiMain $main,
22        string $name,
23        VisualEditorParsoidClientFactory $parsoidClientFactory,
24        CommentParser $commentParser,
25        RevisionLookup $revisionLookup
26    ) {
27        parent::__construct( $main, $name );
28        $this->parsoidClientFactory = $parsoidClientFactory;
29        $this->commentParser = $commentParser;
30        $this->revisionLookup = $revisionLookup;
31    }
32
33    /**
34     * @inheritDoc
35     */
36    public function execute() {
37        $params = $this->extractRequestParams();
38
39        $this->requireOnlyOneParameter( $params, 'fromtitle', 'fromrev' );
40        $this->requireOnlyOneParameter( $params, 'totitle', 'torev' );
41
42        if ( isset( $params['torev'] ) ) {
43            $toRev = $this->revisionLookup->getRevisionById( $params['torev'] );
44            if ( !$toRev ) {
45                $this->dieWithError( [ 'apierror-nosuchrevid', $params['torev'] ] );
46            }
47
48        } else {
49            $toTitle = Title::newFromText( $params['totitle'] );
50            if ( !$toTitle ) {
51                $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['totitle'] ) ] );
52            }
53            $toRev = $this->revisionLookup->getRevisionByTitle( $toTitle );
54            if ( !$toRev ) {
55                $this->dieWithError(
56                    [ 'apierror-missingrev-title', wfEscapeWikiText( $toTitle->getPrefixedText() ) ],
57                    'nosuchrevid'
58                );
59            }
60        }
61
62        // When polling for new comments this is an important optimisation,
63        // as usually there is no new revision.
64        if ( $toRev->getId() === $params['fromrev'] ) {
65            $this->addResult( $toRev, $toRev );
66            return;
67        }
68
69        if ( isset( $params['fromrev'] ) ) {
70            $fromRev = $this->revisionLookup->getRevisionById( $params['fromrev'] );
71            if ( !$fromRev ) {
72                $this->dieWithError( [ 'apierror-nosuchrevid', $params['fromrev'] ] );
73            }
74
75        } else {
76            $fromTitle = Title::newFromText( $params['fromtitle'] );
77            if ( !$fromTitle ) {
78                $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['fromtitle'] ) ] );
79            }
80            $fromRev = $this->revisionLookup->getRevisionByTitle( $fromTitle );
81            if ( !$fromRev ) {
82                $this->dieWithError(
83                    [ 'apierror-missingrev-title', wfEscapeWikiText( $fromTitle->getPrefixedText() ) ],
84                    'nosuchrevid'
85                );
86            }
87        }
88
89        if ( $fromRev->hasSameContent( $toRev ) ) {
90            $this->addResult( $fromRev, $toRev );
91            return;
92        }
93
94        $fromItemSet = HookUtils::parseRevisionParsoidHtml( $fromRev );
95        $toItemSet = HookUtils::parseRevisionParsoidHtml( $toRev );
96
97        $removedComments = [];
98        foreach ( $fromItemSet->getCommentItems() as $fromComment ) {
99            if ( !$toItemSet->findCommentById( $fromComment->getId() ) ) {
100                $removedComments[] = $fromComment->jsonSerializeForDiff();
101            }
102        }
103
104        $addedComments = [];
105        foreach ( $toItemSet->getCommentItems() as $toComment ) {
106            if ( !$fromItemSet->findCommentById( $toComment->getId() ) ) {
107                $addedComments[] = $toComment->jsonSerializeForDiff();
108            }
109        }
110
111        $this->addResult( $fromRev, $toRev, $removedComments, $addedComments );
112    }
113
114    /**
115     * Add the result object from revisions and comment lists
116     *
117     * @param RevisionRecord $fromRev From revision
118     * @param RevisionRecord $toRev To revision
119     * @param array $removedComments Removed comments
120     * @param array $addedComments Added comments
121     */
122    protected function addResult(
123        RevisionRecord $fromRev, RevisionRecord $toRev, array $removedComments = [], array $addedComments = []
124    ) {
125        $fromTitle = Title::newFromLinkTarget(
126            $fromRev->getPageAsLinkTarget()
127        );
128        $toTitle = Title::newFromLinkTarget(
129            $toRev->getPageAsLinkTarget()
130        );
131        $result = [
132            'fromrevid' => $fromRev->getId(),
133            'fromtitle' => $fromTitle->getPrefixedText(),
134            'torevid' => $toRev->getId(),
135            'totitle' => $toTitle->getPrefixedText(),
136            'removedcomments' => $removedComments,
137            'addedcomments' => $addedComments,
138        ];
139        $this->getResult()->addValue( null, $this->getModuleName(), $result );
140    }
141
142    /**
143     * @inheritDoc
144     */
145    public function getAllowedParams() {
146        return [
147            'fromtitle' => [
148                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-fromtitle',
149            ],
150            'fromrev' => [
151                ParamValidator::PARAM_TYPE => 'integer',
152                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-fromrev',
153            ],
154            'totitle' => [
155                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-totitle',
156            ],
157            'torev' => [
158                ParamValidator::PARAM_TYPE => 'integer',
159                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-torev',
160            ],
161        ];
162    }
163
164    /**
165     * @inheritDoc
166     */
167    public function needsToken() {
168        return false;
169    }
170
171    /**
172     * @inheritDoc
173     */
174    public function isInternal() {
175        return true;
176    }
177
178    /**
179     * @inheritDoc
180     */
181    public function isWriteMode() {
182        return false;
183    }
184}