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