Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.08% covered (warning)
73.08%
57 / 78
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiDiscussionToolsCompare
73.08% covered (warning)
73.08%
57 / 78
62.50% covered (warning)
62.50%
5 / 8
27.81
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
 getRevision
37.50% covered (danger)
37.50%
6 / 16
0.00% covered (danger)
0.00%
0 / 1
14.79
 execute
58.33% covered (warning)
58.33%
14 / 24
0.00% covered (danger)
0.00%
0 / 1
12.63
 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     * @throws ApiUsageException
37     */
38    private function getRevision( array $params, string $prefix ): RevisionRecord {
39        if ( isset( $params["{$prefix}rev"] ) ) {
40            $rev = $this->revisionLookup->getRevisionById( $params["{$prefix}rev"] );
41            if ( !$rev ) {
42                $this->dieWithError( [ 'apierror-nosuchrevid', $params["{$prefix}rev"] ] );
43            }
44
45        } else {
46            $title = Title::newFromText( $params["{$prefix}title"] );
47            if ( !$title ) {
48                $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ] );
49            }
50            $rev = $this->revisionLookup->getRevisionByTitle( $title );
51            if ( !$rev ) {
52                $this->dieWithError(
53                    [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ],
54                    'nosuchrevid'
55                );
56            }
57        }
58        // To keep things simple, don't allow viewing deleted revisions through this API
59        // (even if the current user could view them if we checked with userCan()).
60        if ( !$rev->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
61            $this->dieWithError( [ 'apierror-missingcontent-revid', $rev->getId() ], 'missingcontent' );
62        }
63        return $rev;
64    }
65
66    /**
67     * @inheritDoc
68     * @throws ApiUsageException
69     */
70    public function execute() {
71        $params = $this->extractRequestParams();
72
73        $this->requireOnlyOneParameter( $params, 'fromtitle', 'fromrev' );
74        $this->requireOnlyOneParameter( $params, 'totitle', 'torev' );
75
76        $toRev = $this->getRevision( $params, 'to' );
77
78        // When polling for new comments this is an important optimisation,
79        // as usually there is no new revision.
80        if ( $toRev->getId() === $params['fromrev'] ) {
81            $this->addResult( $toRev, $toRev );
82            return;
83        }
84
85        $fromRev = $this->getRevision( $params, 'from' );
86
87        if ( $fromRev->hasSameContent( $toRev ) ) {
88            $this->addResult( $fromRev, $toRev );
89            return;
90        }
91
92        try {
93            $fromItemSet = HookUtils::parseRevisionParsoidHtml( $fromRev, __METHOD__ );
94            $toItemSet = HookUtils::parseRevisionParsoidHtml( $toRev, __METHOD__ );
95        } catch ( ResourceLimitExceededException $e ) {
96            $this->dieWithException( $e );
97        }
98
99        $removedComments = [];
100        foreach ( $fromItemSet->getCommentItems() as $fromComment ) {
101            if ( !$toItemSet->findCommentById( $fromComment->getId() ) ) {
102                $removedComments[] = $fromComment->jsonSerializeForDiff();
103            }
104        }
105
106        $addedComments = [];
107        foreach ( $toItemSet->getCommentItems() as $toComment ) {
108            if ( !$fromItemSet->findCommentById( $toComment->getId() ) ) {
109                $addedComments[] = $toComment->jsonSerializeForDiff();
110            }
111        }
112
113        $this->addResult( $fromRev, $toRev, $removedComments, $addedComments );
114    }
115
116    /**
117     * Add the result object from revisions and comment lists
118     *
119     * @param RevisionRecord $fromRev From revision
120     * @param RevisionRecord $toRev To revision
121     * @param array $removedComments Removed comments
122     * @param array $addedComments Added comments
123     */
124    protected function addResult(
125        RevisionRecord $fromRev, RevisionRecord $toRev, array $removedComments = [], array $addedComments = []
126    ) {
127        $fromTitle = Title::newFromLinkTarget(
128            $fromRev->getPageAsLinkTarget()
129        );
130        $toTitle = Title::newFromLinkTarget(
131            $toRev->getPageAsLinkTarget()
132        );
133        $result = [
134            'fromrevid' => $fromRev->getId(),
135            'fromtitle' => $fromTitle->getPrefixedText(),
136            'torevid' => $toRev->getId(),
137            'totitle' => $toTitle->getPrefixedText(),
138            'removedcomments' => $removedComments,
139            'addedcomments' => $addedComments,
140        ];
141        $this->getResult()->addValue( null, $this->getModuleName(), $result );
142    }
143
144    /**
145     * @inheritDoc
146     */
147    public function getAllowedParams() {
148        return [
149            'fromtitle' => [
150                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-fromtitle',
151            ],
152            'fromrev' => [
153                ParamValidator::PARAM_TYPE => 'integer',
154                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-fromrev',
155            ],
156            'totitle' => [
157                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-totitle',
158            ],
159            'torev' => [
160                ParamValidator::PARAM_TYPE => 'integer',
161                ApiBase::PARAM_HELP_MSG => 'apihelp-compare-param-torev',
162            ],
163        ];
164    }
165
166    /**
167     * @inheritDoc
168     */
169    public function needsToken() {
170        return false;
171    }
172
173    /**
174     * @inheritDoc
175     */
176    public function isInternal() {
177        return true;
178    }
179
180    /**
181     * @inheritDoc
182     */
183    public function isWriteMode() {
184        return false;
185    }
186}