Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
53.85% |
42 / 78 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
PageMessageBuilder | |
53.85% |
42 / 78 |
|
0.00% |
0 / 6 |
75.01 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getContent | |
89.29% |
25 / 28 |
|
0.00% |
0 / 1 |
6.04 | |||
getContentWithFallback | |
89.47% |
17 / 19 |
|
0.00% |
0 / 1 |
6.04 | |||
parseGetSectionResponse | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getPageContent | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
isNotFoundError | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\MassMessage\PageMessage; |
5 | |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Languages\LanguageFallback; |
8 | use MediaWiki\Languages\LanguageNameUtils; |
9 | use MediaWiki\MassMessage\LanguageAwareText; |
10 | use MediaWiki\MassMessage\MessageContentFetcher\LabeledSectionContentFetcher; |
11 | use MediaWiki\MassMessage\MessageContentFetcher\LocalMessageContentFetcher; |
12 | use MediaWiki\MassMessage\MessageContentFetcher\RemoteMessageContentFetcher; |
13 | use MediaWiki\Status\Status; |
14 | use MediaWiki\Title\Title; |
15 | |
16 | /** |
17 | * Contains logic to interact with page being sent as a mesasges |
18 | * @author Abijeet Patro |
19 | * @since 2022.01 |
20 | * @license GPL-2.0-or-later |
21 | */ |
22 | class PageMessageBuilder { |
23 | /** @var string */ |
24 | private $currentWikiId; |
25 | /** @var LocalMessageContentFetcher */ |
26 | private $localMessageContentFetcher; |
27 | /** @var LabeledSectionContentFetcher */ |
28 | private $labeledSectionContentFetcher; |
29 | /** @var RemoteMessageContentFetcher */ |
30 | private $remoteMessageContentFetcher; |
31 | /** @var LanguageNameUtils */ |
32 | private $languageNameUtils; |
33 | /** @var LanguageFallback */ |
34 | private $languageFallback; |
35 | |
36 | /** |
37 | * @param LocalMessageContentFetcher $localMessageContentFetcher |
38 | * @param LabeledSectionContentFetcher $labeledSectionContentFetcher |
39 | * @param RemoteMessageContentFetcher $remoteMessageContentFetcher |
40 | * @param LanguageNameUtils $languageNameUtils |
41 | * @param LanguageFallback $languageFallback |
42 | * @param string $currentWikiId |
43 | */ |
44 | public function __construct( |
45 | LocalMessageContentFetcher $localMessageContentFetcher, |
46 | LabeledSectionContentFetcher $labeledSectionContentFetcher, |
47 | RemoteMessageContentFetcher $remoteMessageContentFetcher, |
48 | LanguageNameUtils $languageNameUtils, |
49 | LanguageFallback $languageFallback, |
50 | string $currentWikiId |
51 | ) { |
52 | $this->localMessageContentFetcher = $localMessageContentFetcher; |
53 | $this->labeledSectionContentFetcher = $labeledSectionContentFetcher; |
54 | $this->remoteMessageContentFetcher = $remoteMessageContentFetcher; |
55 | $this->languageNameUtils = $languageNameUtils; |
56 | $this->languageFallback = $languageFallback; |
57 | $this->currentWikiId = $currentWikiId; |
58 | } |
59 | |
60 | /** |
61 | * Fetch content from a page or section of a page in a wiki to be used as the subject or |
62 | * in the message body for a MassMessage |
63 | * |
64 | * @param string $pageName |
65 | * @param string|null $pageMessageSection |
66 | * @param string|null $pageSubjectSection |
67 | * @param string $sourceWikiId |
68 | * @return PageMessageBuilderResult |
69 | */ |
70 | public function getContent( |
71 | string $pageName, |
72 | ?string $pageMessageSection, |
73 | ?string $pageSubjectSection, |
74 | string $sourceWikiId |
75 | ): PageMessageBuilderResult { |
76 | if ( $pageName === '' ) { |
77 | throw new InvalidArgumentException( 'Empty page name passed' ); |
78 | } |
79 | |
80 | $pageContentStatus = $this->getPageContent( $pageName, $sourceWikiId ); |
81 | if ( !$pageContentStatus->isOK() ) { |
82 | return new PageMessageBuilderResult( $pageContentStatus ); |
83 | } |
84 | |
85 | /** @var LanguageAwareText */ |
86 | $pageContent = $pageContentStatus->getValue(); |
87 | if ( $pageContent->getWikitext() === '' ) { |
88 | return new PageMessageBuilderResult( Status::newFatal( 'massmessage-page-message-empty', $pageName ) ); |
89 | } |
90 | |
91 | $pageMessage = $pageContent; |
92 | $pageSubject = null; |
93 | $finalStatus = $pageContentStatus; |
94 | |
95 | if ( $pageMessageSection ) { |
96 | // Include section tags for backwards compatibility. |
97 | // https://phabricator.wikimedia.org/T254481#6865334 |
98 | $messageSectionStatus = $this->labeledSectionContentFetcher |
99 | ->getContent( $pageContent, $pageMessageSection ); |
100 | $pageMessage = $this->parseGetSectionResponse( |
101 | $messageSectionStatus, |
102 | $finalStatus, |
103 | Status::newFatal( 'massmessage-page-message-empty', $pageName ) |
104 | ); |
105 | } |
106 | |
107 | if ( $pageSubjectSection ) { |
108 | $subjectSectionStatus = $this->labeledSectionContentFetcher |
109 | ->getContentWithoutTags( $pageContent, $pageSubjectSection ); |
110 | $pageSubject = $this->parseGetSectionResponse( |
111 | $subjectSectionStatus, |
112 | $finalStatus, |
113 | Status::newFatal( 'massmessage-page-subject-empty', $pageSubjectSection, $pageName ) |
114 | ); |
115 | } |
116 | |
117 | return new PageMessageBuilderResult( $finalStatus, $pageMessage, $pageSubject ); |
118 | } |
119 | |
120 | /** |
121 | * Get content for a target language from wiki, using fallbacks if necessary |
122 | * |
123 | * @param string $titleStr |
124 | * @param string $targetLangCode |
125 | * @param string $sourceLangCode |
126 | * @param string|null $pageMessageSection |
127 | * @param string|null $pageSubjectSection |
128 | * @param string $sourceWikiId |
129 | * @return PageMessageBuilderResult Values is LanguageAwareText or null on failure |
130 | */ |
131 | public function getContentWithFallback( |
132 | string $titleStr, |
133 | string $targetLangCode, |
134 | string $sourceLangCode, |
135 | ?string $pageMessageSection, |
136 | ?string $pageSubjectSection, |
137 | string $sourceWikiId |
138 | ): PageMessageBuilderResult { |
139 | if ( !$this->languageNameUtils->isKnownLanguageTag( $targetLangCode ) ) { |
140 | return new PageMessageBuilderResult( Status::newFatal( 'massmessage-invalid-lang', $targetLangCode ) ); |
141 | } |
142 | |
143 | // Identify languages to fetch |
144 | $fallbackChain = array_merge( |
145 | [ $targetLangCode ], |
146 | $this->languageFallback->getAll( $targetLangCode ) |
147 | ); |
148 | |
149 | foreach ( $fallbackChain as $langCode ) { |
150 | $titleStrWithLang = $titleStr . '/' . $langCode; |
151 | $pageMessageBuilderResult = $this->getContent( |
152 | $titleStrWithLang, $pageMessageSection, $pageSubjectSection, $sourceWikiId |
153 | ); |
154 | |
155 | if ( $pageMessageBuilderResult->isOK() ) { |
156 | return $pageMessageBuilderResult; |
157 | } |
158 | |
159 | // Got an unknown error, let's stop looking for other fallbacks |
160 | if ( !$this->isNotFoundError( $pageMessageBuilderResult->getStatus() ) ) { |
161 | break; |
162 | } |
163 | } |
164 | |
165 | // No language or fallback found or there was an error, go with source language |
166 | $langSuffix = ''; |
167 | if ( $sourceLangCode ) { |
168 | $langSuffix = "/$sourceLangCode"; |
169 | } |
170 | |
171 | return $this->getContent( $titleStr . $langSuffix, $pageMessageSection, $pageSubjectSection, $sourceWikiId ); |
172 | } |
173 | |
174 | /** |
175 | * Helper method to parse response from get labeled section method and updates the passed status |
176 | * |
177 | * @param Status $sectionStatus Status from get labeled section |
178 | * @param Status $statusToUpdate Status to update |
179 | * @param Status $emptySectionErrorStatus Fatal status to use if section content is empty |
180 | * @return LanguageAwareText|null |
181 | */ |
182 | private function parseGetSectionResponse( |
183 | Status $sectionStatus, |
184 | Status $statusToUpdate, |
185 | Status $emptySectionErrorStatus |
186 | ): ?LanguageAwareText { |
187 | if ( !$sectionStatus->isOK() ) { |
188 | $statusToUpdate = $statusToUpdate->merge( $sectionStatus ); |
189 | } else { |
190 | /** @var LanguageAwareText */ |
191 | $sectionContent = $sectionStatus->getValue(); |
192 | if ( $sectionContent->getWikitext() === '' ) { |
193 | $statusToUpdate->merge( $emptySectionErrorStatus ); |
194 | } else { |
195 | return $sectionContent; |
196 | } |
197 | } |
198 | return null; |
199 | } |
200 | |
201 | /** |
202 | * Uses the database or API to fetch content based on the wiki. |
203 | * |
204 | * @param string $titleStr |
205 | * @param string $wikiId |
206 | * @return Status Values is LanguageAwareText or null on failure |
207 | */ |
208 | private function getPageContent( string $titleStr, string $wikiId ): Status { |
209 | $isCurrentWiki = $this->currentWikiId === $wikiId; |
210 | $title = Title::newFromText( $titleStr ); |
211 | if ( $title === null ) { |
212 | return Status::newFatal( |
213 | 'massmessage-page-message-invalid', $titleStr |
214 | ); |
215 | } |
216 | |
217 | if ( $isCurrentWiki ) { |
218 | return $this->localMessageContentFetcher->getContent( $title ); |
219 | } |
220 | |
221 | return $this->remoteMessageContentFetcher->getContent( $titleStr, $wikiId ); |
222 | } |
223 | |
224 | /** |
225 | * Checks if a given Status is a not found error. |
226 | * |
227 | * @param Status $status |
228 | * @return bool |
229 | */ |
230 | private function isNotFoundError( Status $status ): bool { |
231 | $notFoundErrors = [ |
232 | 'massmessage-page-message-not-found', 'massmessage-page-message-not-found-in-wiki' |
233 | ]; |
234 | $errors = $status->getErrors(); |
235 | if ( $errors ) { |
236 | foreach ( $errors as $error ) { |
237 | if ( in_array( $error['message'], $notFoundErrors ) ) { |
238 | return true; |
239 | } |
240 | } |
241 | } |
242 | |
243 | return false; |
244 | } |
245 | } |