Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoteMessageContentFetcher
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 4
132
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getContent
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
20
 parseQueryApiResponse
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 getApiEndpoint
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\MassMessage\MessageContentFetcher;
5
6use MediaWiki\Config\SiteConfiguration;
7use MediaWiki\Http\HttpRequestFactory;
8use MediaWiki\MassMessage\LanguageAwareText;
9use MediaWiki\Status\Status;
10
11/**
12 * Fetches content from a remote wiki
13 * @author Abijeet Patro
14 * @since 2022.01
15 * @license GPL-2.0-or-later
16 */
17class RemoteMessageContentFetcher {
18    /** @var HttpRequestFactory */
19    private $httpRequestFactory;
20    /** @var SiteConfiguration */
21    private $siteConfiguration;
22
23    /**
24     * @param HttpRequestFactory $requestFactory
25     * @param SiteConfiguration $siteConfiguration
26     */
27    public function __construct( HttpRequestFactory $requestFactory, SiteConfiguration $siteConfiguration ) {
28        $this->httpRequestFactory = $requestFactory;
29        $this->siteConfiguration = $siteConfiguration;
30    }
31
32    /**
33     * Fetch remote content and return Status object. The Status value contains a LanguageAwareText object
34     * @param string $pageTitle
35     * @param string $wikiId
36     * @return Status
37     */
38    public function getContent( string $pageTitle, string $wikiId ): Status {
39        $apiUrl = $this->getApiEndpoint( $wikiId );
40        if ( !$apiUrl ) {
41            return Status::newFatal(
42                'massmessage-page-message-wiki-not-found',
43                $wikiId,
44                $pageTitle
45            );
46        }
47
48        $queryParams = [
49            'action' => 'query',
50            'format' => 'json',
51            'prop' => 'info|revisions',
52            'rvprop' => 'content',
53            'rvslots' => 'main',
54            'titles' => $pageTitle,
55            'formatversion' => 2,
56        ];
57
58        $options = [
59            'method' => 'GET',
60            'timeout' => 15,
61        ];
62
63        $apiUrl .= '?' . http_build_query( $queryParams );
64        $req = $this->httpRequestFactory->create( $apiUrl, $options, __METHOD__ );
65
66        $status = $req->execute();
67        if ( !$status->isOK() ) {
68            // FIXME: Formatting is broken here, needs to be improved.
69            return Status::newFatal(
70                "massmessage-page-message-fetch-error-in-wiki",
71                $wikiId,
72                $pageTitle,
73                $status->getMessage()->text()
74            );
75        }
76
77        $json = $req->getContent();
78        $response = json_decode( $json, true );
79        if ( $response === null ) {
80            return Status::newFatal(
81                "massmessage-page-message-parsing-error-in-wiki",
82                $wikiId,
83                $pageTitle,
84                json_last_error_msg()
85            );
86        }
87
88        return $this->parseQueryApiResponse( $response, $wikiId, $pageTitle, $json );
89    }
90
91    /**
92     * @param array $response
93     * @param string $wikiId
94     * @param string $pageTitle
95     * @param string $json
96     * @return Status
97     */
98    private function parseQueryApiResponse(
99        array $response,
100        string $wikiId,
101        string $pageTitle,
102        string $json
103    ): Status {
104        // Example response:
105        // {
106        //   "batchcomplete": true,
107        //   "query": {
108        //     "pages": [ {
109        //       "pageid": 11285354,
110        //       "ns": 0,
111        //       "title": "Tech/News/2021/12",
112        //       "contentmodel": "wikitext",
113        //       "pagelanguage": "en",
114        //       "pagelanguagehtmlcode": "en",
115        //       "pagelanguagedir": "ltr",
116        //       "touched": "2021-03-23T06:05:06Z",
117        //       "lastrevid": 21247464,
118        //       "length": 4585,
119        //       "revisions": [ {
120        //         "slots": {
121        //           "main": {
122        //             "contentmodel": "wikitext",
123        //             "contentformat": "text/x-wiki",
124        //             "content": "[...]"
125        //           }
126        //         }
127        //       } ]
128        //     } ]
129        //   }
130        // }
131
132        $pages = $response['query']['pages'] ?? [];
133        if ( isset( $response['error']['info'] ) || count( $pages ) !== 1 ) {
134            return Status::newFatal(
135                'massmessage-page-message-parse-invalid-in-wiki',
136                $wikiId,
137                $pageTitle,
138                $response['error']['info'] ?? $json
139            );
140        }
141
142        // Take first and only one out of the list
143        $page = current( $pages );
144
145        if ( isset( $page['missing'] ) ) {
146            // Page was not found
147            return Status::newFatal(
148                'massmessage-page-message-not-found-in-wiki',
149                $wikiId,
150                $pageTitle
151            );
152        }
153
154        $content = new LanguageAwareText(
155            $page['revisions'][0]['slots']['main']['content'],
156            $page['pagelanguage'],
157            $page['pagelanguagedir']
158        );
159
160        return Status::newGood( $content );
161    }
162
163    /**
164     * @param string $wiki
165     * @return string|null
166     */
167    private function getApiEndpoint( string $wiki ): ?string {
168        $this->siteConfiguration->loadFullData();
169
170        $siteFromDB = $this->siteConfiguration->siteFromDB( $wiki );
171        [ $major, $minor ] = $siteFromDB;
172
173        if ( $major === null ) {
174            return null;
175        }
176
177        $configOpts = [ 'lang' => $minor, 'site' => $major ];
178        $server = $this->siteConfiguration->get(
179            'wgServer',
180            $wiki,
181            null,
182            $configOpts
183        );
184        $scriptPath = $this->siteConfiguration->get(
185            'wgScriptPath',
186            $wiki,
187            null,
188            $configOpts
189        );
190
191        $apiPath = wfExpandUrl( $server . $scriptPath . '/api.php', PROTO_INTERNAL );
192
193        return $apiPath;
194    }
195}