Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
23.33% covered (danger)
23.33%
14 / 60
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
DirectParsoidClient
23.33% covered (danger)
23.33%
14 / 60
0.00% covered (danger)
0.00%
0 / 8
102.32
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHtmlOutputRendererHelper
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getHtmlInputTransformHelper
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 makeFakeRevision
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 convertWikitextToHtml
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
2.15
 convertHtmlToWikitext
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
2.02
 fakeRESTbaseHTMLResponse
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 fakeRESTbaseError
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Helper functions for using the REST interface to Parsoid.
4 *
5 * @copyright See AUTHORS.txt
6 * @license GPL-2.0-or-later
7 */
8
9namespace ContentTranslation;
10
11use Exception;
12use MediaWiki\Content\WikitextContent;
13use MediaWiki\Exception\LocalizedException;
14use MediaWiki\Language\RawMessage;
15use MediaWiki\Page\PageIdentity;
16use MediaWiki\Permissions\Authority;
17use MediaWiki\Rest\Handler\Helper\HtmlInputTransformHelper;
18use MediaWiki\Rest\Handler\Helper\HtmlOutputRendererHelper;
19use MediaWiki\Rest\Handler\Helper\PageRestHelperFactory;
20use MediaWiki\Rest\HttpException;
21use MediaWiki\Rest\LocalizedHttpException;
22use MediaWiki\Revision\MutableRevisionRecord;
23use MediaWiki\Revision\RevisionRecord;
24use MediaWiki\User\User;
25
26class DirectParsoidClient implements ParsoidClient {
27
28    /**
29     * Requested Parsoid HTML version.
30     * Keep this in sync with the Accept: header in
31     * ve.init.mw.ArticleTargetLoader.js
32     */
33    public const PARSOID_VERSION = '2.6.0';
34
35    public function __construct(
36        private readonly PageRestHelperFactory $helperFactory,
37        private readonly Authority $performer
38    ) {
39    }
40
41    /**
42     * @param PageIdentity $page
43     * @param RevisionRecord|null $revision
44     *
45     * @return HtmlOutputRendererHelper
46     */
47    private function getHtmlOutputRendererHelper(
48        PageIdentity $page,
49        ?RevisionRecord $revision = null
50    ): HtmlOutputRendererHelper {
51        // TODO: remove this once we no longer need a User object for rate limiting (T310476).
52        if ( $this->performer instanceof User ) {
53            $user = $this->performer;
54        } else {
55            $user = User::newFromIdentity( $this->performer->getUser() );
56        }
57        $helper = $this->helperFactory->newHtmlOutputRendererHelper( $page, [], $user, null );
58
59        if ( $revision ) {
60            $helper->setRevision( $revision );
61        }
62
63        return $helper;
64    }
65
66    /**
67     * @param PageIdentity $page
68     * @param string $html
69     *
70     * @return HtmlInputTransformHelper
71     */
72    private function getHtmlInputTransformHelper(
73        PageIdentity $page,
74        string $html
75    ): HtmlInputTransformHelper {
76        // Fake REST body
77        $body = [
78            'html' => [
79                'body' => $html,
80            ]
81        ];
82
83        $helper = $this->helperFactory->newHtmlInputTransformHelper(
84            [], $page, $body, [], null, null
85        );
86
87        return $helper;
88    }
89
90    /**
91     * @param PageIdentity $page
92     * @param string $wikitext
93     *
94     * @return RevisionRecord
95     */
96    private function makeFakeRevision(
97        PageIdentity $page,
98        string $wikitext
99    ): RevisionRecord {
100        $rev = MutableRevisionRecord::newFromContent( $page, new WikitextContent( $wikitext ) );
101        $rev->setId( 0 );
102        $rev->setPageId( $page->getId() );
103
104        return $rev;
105    }
106
107    /**
108     * Transform wikitext to HTML with Parsoid. Wrapper for ::postData().
109     *
110     * @param PageIdentity $page The page the content belongs to use as the parsing context
111     * @param string $wikitext The wikitext fragment to parse
112     *
113     * @return array An array mimicking a RESTbase server's response,
114     *   with keys 'code', 'reason', 'headers' and 'body'
115     */
116    public function convertWikitextToHtml(
117        PageIdentity $page,
118        string $wikitext
119    ): array {
120        $revision = $this->makeFakeRevision( $page, $wikitext );
121        $helper = $this->getHtmlOutputRendererHelper( $page, $revision );
122
123        try {
124            $html = $helper->getPageBundle()->html;
125            return $this->fakeRESTbaseHTMLResponse( $html, $helper );
126        } catch ( HttpException $ex ) {
127            return $this->fakeRESTbaseError( $ex );
128        }
129    }
130
131    /**
132     * Transform HTML to wikitext with Parsoid
133     *
134     * @param PageIdentity $page The page the content belongs to
135     * @param string $html The HTML of the page to be transformed
136     *
137     * @return array The response, 'code', 'reason', 'headers' and 'body'
138     */
139    public function convertHtmlToWikitext(
140        PageIdentity $page, string $html
141    ): array {
142        $helper = $this->getHtmlInputTransformHelper( $page, $html );
143
144        try {
145            $content = $helper->getContent();
146            $format = $content->getDefaultFormat();
147
148            return [
149                'code' => 200,
150                'headers' => [
151                    'Content-Type' => $format,
152                ],
153                'body' => $content->serialize( $format ),
154            ];
155        } catch ( HttpException $ex ) {
156            return $this->fakeRESTbaseError( $ex );
157        }
158    }
159
160    /**
161     * @param mixed $data
162     * @param HtmlOutputRendererHelper $helper
163     *
164     * @return array
165     */
166    private function fakeRESTbaseHTMLResponse( $data, HtmlOutputRendererHelper $helper ): array {
167        return [
168            'code' => 200,
169            'headers' => [
170                'content-language' => $helper->getHtmlOutputContentLanguage(),
171                'etag' => $helper->getETag()
172            ],
173            'body' => $data,
174        ];
175    }
176
177    private function fakeRESTbaseError( Exception $ex ): array {
178        if ( $ex instanceof LocalizedHttpException ) {
179            $msg = $ex->getMessageValue();
180        } elseif ( $ex instanceof LocalizedException ) {
181            $msg = $ex->getMessageObject();
182        } else {
183            $msg = new RawMessage( $ex->getMessage() );
184        }
185
186        return [
187            'error' => [
188                'message' => $msg->getKey(),
189                'params' => $msg->getParams(),
190            ],
191            'headers' => [],
192            'body' => $ex->getMessage(),
193        ];
194    }
195}