Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 83 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
RemoteMessageContentFetcher | |
0.00% |
0 / 83 |
|
0.00% |
0 / 4 |
132 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getContent | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
20 | |||
parseQueryApiResponse | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
getApiEndpoint | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\MassMessage\MessageContentFetcher; |
5 | |
6 | use MediaWiki\Config\SiteConfiguration; |
7 | use MediaWiki\Http\HttpRequestFactory; |
8 | use MediaWiki\MassMessage\LanguageAwareText; |
9 | use 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 | */ |
17 | class 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 | } |