Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 131 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
BannerMessageGroup | |
0.00% |
0 / 131 |
|
0.00% |
0 / 9 |
756 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getKeys | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
getDefinitions | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
isUsingGroupReview | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getTranslateGroupName | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
updateBannerGroupStateHook | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
72 | |||
getMessageGroupStates | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
registerGroupHook | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
6 | |||
getLanguagesInState | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
4 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupStates; |
5 | use MediaWiki\MediaWikiServices; |
6 | |
7 | /** |
8 | * Generate a group of message definitions for a banner so they can be translated |
9 | */ |
10 | class BannerMessageGroup extends WikiMessageGroup { |
11 | |
12 | private const TRANSLATE_GROUP_NAME_BASE = 'Centralnotice-tgroup'; |
13 | |
14 | /** @var string */ |
15 | private $bannerName = ''; |
16 | |
17 | /** |
18 | * @var int |
19 | * @suppress PhanUndeclaredConstant NS_CN_BANNER defined in extension.json |
20 | */ |
21 | protected $namespace = NS_CN_BANNER; |
22 | |
23 | /** |
24 | * @param int $namespace ID of the namespace holding CentralNotice messages |
25 | * @param string $title The page name of the CentralNotice banner |
26 | */ |
27 | public function __construct( $namespace, $title ) { |
28 | $titleObj = Title::makeTitle( $namespace, $title ); |
29 | $this->id = static::getTranslateGroupName( $title ); |
30 | |
31 | // For internal usage we just want the name of the banner. In the MediaWiki namespace |
32 | // this is stored with a prefix. Elsewhere (like the CentralNotice namespace) it is |
33 | // just the page name. |
34 | $this->bannerName = str_replace( 'Centralnotice-template-', '', $title ); |
35 | |
36 | // And now set the label for the Translate UI |
37 | $this->setLabel( $titleObj->getPrefixedText() ); |
38 | } |
39 | |
40 | /** |
41 | * This is optimized version of getDefinitions that only returns |
42 | * message keys to speed up message index creation. |
43 | * @return array |
44 | */ |
45 | public function getKeys() { |
46 | $keys = []; |
47 | |
48 | $banner = Banner::fromName( $this->bannerName ); |
49 | $fields = $banner->getMessageFieldsFromCache(); |
50 | |
51 | // The MediaWiki page name convention for messages is the same as the |
52 | // convention for banners themselves, except that it doesn't include |
53 | // the 'template' designation. |
54 | if ( $this->namespace === NS_CN_BANNER ) { |
55 | $msgKeyPrefix = $this->bannerName . '-'; |
56 | } else { |
57 | $msgKeyPrefix = "Centralnotice-{$this->bannerName}-"; |
58 | } |
59 | |
60 | foreach ( array_keys( $fields ) as $msgName ) { |
61 | $keys[] = $msgKeyPrefix . $msgName; |
62 | } |
63 | |
64 | return $keys; |
65 | } |
66 | |
67 | /** |
68 | * Fetch the messages for the banner |
69 | * @return array Array of message keys with definitions. |
70 | */ |
71 | public function getDefinitions() { |
72 | $definitions = []; |
73 | |
74 | $banner = Banner::fromName( $this->bannerName ); |
75 | $fields = $banner->getMessageFieldsFromCache(); |
76 | |
77 | // The MediaWiki page name convention for messages is the same as the |
78 | // convention for banners themselves, except that it doesn't include |
79 | // the 'template' designation. |
80 | $msgDefKeyPrefix = "Centralnotice-{$this->bannerName}-"; |
81 | if ( $this->namespace === NS_CN_BANNER ) { |
82 | $msgKeyPrefix = $this->bannerName . '-'; |
83 | } else { |
84 | $msgKeyPrefix = $msgDefKeyPrefix; |
85 | } |
86 | |
87 | // Build the array of message definitions. |
88 | foreach ( $fields as $msgName => $msgCount ) { |
89 | $defkey = $msgDefKeyPrefix . $msgName; |
90 | $msgkey = $msgKeyPrefix . $msgName; |
91 | $definitions[$msgkey] = wfMessage( $defkey )->inContentLanguage()->plain(); |
92 | } |
93 | |
94 | return $definitions; |
95 | } |
96 | |
97 | /** |
98 | * Determine if the CentralNotice banner group is using the group review feature of translate |
99 | * @return bool |
100 | */ |
101 | public static function isUsingGroupReview() { |
102 | static $useGroupReview = null; |
103 | |
104 | if ( $useGroupReview === null ) { |
105 | $group = MessageGroups::getGroup( self::TRANSLATE_GROUP_NAME_BASE ); |
106 | if ( $group && $group->getMessageGroupStates() ) { |
107 | $useGroupReview = true; |
108 | } else { |
109 | $useGroupReview = false; |
110 | } |
111 | } |
112 | |
113 | return $useGroupReview; |
114 | } |
115 | |
116 | /** |
117 | * Constructs the translate group name from any number of alternate forms. The group name is |
118 | * defined to be 'Centralnotice-tgroup-<BannerName>' |
119 | * |
120 | * This function can handle input in the form of: |
121 | * - raw banner name |
122 | * - Centralnotice-template-<banner name> |
123 | * |
124 | * @param string $bannerName The name of the banner |
125 | * |
126 | * @return string Canonical translate group name |
127 | */ |
128 | public static function getTranslateGroupName( $bannerName ) { |
129 | if ( str_starts_with( $bannerName, 'Centralnotice-template' ) ) { |
130 | return str_replace( |
131 | 'Centralnotice-template', |
132 | self::TRANSLATE_GROUP_NAME_BASE, |
133 | $bannerName |
134 | ); |
135 | } else { |
136 | return self::TRANSLATE_GROUP_NAME_BASE . '-' . $bannerName; |
137 | } |
138 | } |
139 | |
140 | /** |
141 | * Hook to handle message group review state changes. If the $newState |
142 | * for a group is equal to @see $wgNoticeTranslateDeployStates then this |
143 | * function will copy from the CNBanners namespace into the MW namespace |
144 | * and protect them with right $wgCentralNoticeMessageProtectRight. This |
145 | * implies that the user calling this hook must have site-edit permissions |
146 | * and the $wgCentralNoticeMessageProtectRight granted. |
147 | * |
148 | * @param MessageGroup $group Effected group object |
149 | * @param string $code Language code that was modified |
150 | * @param string $currentState Review state the group is transitioning from |
151 | * @param string $newState Review state the group is transitioning to |
152 | * |
153 | * @return bool |
154 | */ |
155 | public static function updateBannerGroupStateHook( $group, $code, $currentState, $newState ) { |
156 | global $wgNoticeTranslateDeployStates; |
157 | |
158 | // We only need to run this if we're actually using group review |
159 | if ( !self::isUsingGroupReview() ) { |
160 | return true; |
161 | } |
162 | |
163 | if ( $group instanceof AggregateMessageGroup ) { |
164 | // Deal with an aggregate group object having changed |
165 | $groups = $group->getGroups(); |
166 | foreach ( $groups as $subgroup ) { |
167 | self::updateBannerGroupStateHook( |
168 | $subgroup, $code, $currentState, $newState ); |
169 | } |
170 | } elseif ( ( $group instanceof BannerMessageGroup ) |
171 | && in_array( $newState, $wgNoticeTranslateDeployStates ) |
172 | ) { |
173 | // Finally an object we can deal with directly and it's in the right state! |
174 | $collection = $group->initCollection( $code ); |
175 | $collection->loadTranslations(); |
176 | $keys = $collection->getMessageKeys(); |
177 | $user = RequestContext::getMain()->getUser(); |
178 | $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory(); |
179 | |
180 | // Now copy each key into the MW namespace |
181 | foreach ( $keys as $key ) { |
182 | $wikiPage = $wikiPageFactory->newFromTitle( |
183 | Title::makeTitleSafe( NS_CN_BANNER, $key . '/' . $code ) |
184 | ); |
185 | |
186 | // Make sure the translation actually exists :p |
187 | if ( $wikiPage->exists() ) { |
188 | $content = $wikiPage->getContent(); |
189 | /** @var TextContent $content */ |
190 | '@phan-var TextContent $content'; |
191 | $text = $content->getText(); |
192 | |
193 | $wikiPage = $wikiPageFactory->newFromTitle( |
194 | Title::makeTitleSafe( NS_MEDIAWIKI, 'Centralnotice-' . $key . '/' . $code ) |
195 | ); |
196 | $wikiPage->doUserEditContent( |
197 | ContentHandler::makeContent( $text, $wikiPage->getTitle() ), |
198 | $user, |
199 | 'Update from translation plugin', |
200 | EDIT_FORCE_BOT, |
201 | false, // $originalRevId |
202 | [ 'centralnotice translation' ] |
203 | ); |
204 | Banner::protectBannerContent( $wikiPage, $user, true ); |
205 | } |
206 | } |
207 | } else { |
208 | // We do nothing; we don't care about this type of group; or it's in the wrong state |
209 | } |
210 | |
211 | return true; |
212 | } |
213 | |
214 | public function getMessageGroupStates() { |
215 | $conf = [ |
216 | 'progress' => [ 'color' => 'E00' ], |
217 | 'proofreading' => [ 'color' => 'FFBF00' ], |
218 | 'ready' => [ 'color' => 'FF0' ], |
219 | 'published' => [ 'color' => 'AEA', 'right' => 'centralnotice-admin' ], |
220 | 'state conditions' => [ |
221 | [ 'ready', [ 'PROOFREAD' => 'MAX' ] ], |
222 | [ 'proofreading', [ 'TRANSLATED' => 'MAX' ] ], |
223 | [ 'progress', [ 'UNTRANSLATED' => 'NONZERO' ] ], |
224 | [ 'unset', [ 'UNTRANSLATED' => 'MAX', 'OUTDATED' => 'ZERO', |
225 | 'TRANSLATED' => 'ZERO' ] ], |
226 | ], |
227 | ]; |
228 | |
229 | return new MessageGroupStates( $conf ); |
230 | } |
231 | |
232 | /** |
233 | * TranslatePostInitGroups hook handler |
234 | * Add banner message groups to the list of message groups that should be |
235 | * translated through the Translate extension. |
236 | * |
237 | * @param array &$list |
238 | * @return bool |
239 | */ |
240 | public static function registerGroupHook( &$list ) { |
241 | // Must be explicitly primary for runs under a jobqueue |
242 | $dbr = CNDatabase::getDb( DB_PRIMARY ); |
243 | |
244 | // Create the base aggregate group |
245 | $conf = []; |
246 | $conf['BASIC'] = [ |
247 | 'id' => self::TRANSLATE_GROUP_NAME_BASE, |
248 | 'label' => 'CentralNotice Banners', |
249 | 'description' => '{{int:centralnotice-aggregate-group-desc}}', |
250 | 'meta' => 1, |
251 | 'class' => 'AggregateMessageGroup', |
252 | 'namespace' => NS_CN_BANNER, |
253 | ]; |
254 | $conf['GROUPS'] = []; |
255 | |
256 | // Find all the banners marked for translation |
257 | $tables = [ 'page', 'revtag' ]; |
258 | $vars = [ 'page_id', 'page_namespace', 'page_title', ]; |
259 | $conds = [ 'page_id=rt_page', 'rt_type' => Banner::TRANSLATE_BANNER_TAG ]; |
260 | $options = [ 'GROUP BY' => 'rt_page, page_id, page_namespace, page_title' ]; |
261 | $res = $dbr->select( $tables, $vars, $conds, __METHOD__, $options ); |
262 | |
263 | foreach ( $res as $r ) { |
264 | $grp = new BannerMessageGroup( $r->page_namespace, $r->page_title ); |
265 | $id = $grp::getTranslateGroupName( $r->page_title ); |
266 | $list[$id] = $grp; |
267 | |
268 | // Add the banner group to the aggregate group |
269 | $conf['GROUPS'][] = $id; |
270 | } |
271 | |
272 | // Update the subgroup meta with any new groups since the last time this was run |
273 | $list[$conf['BASIC']['id']] = MessageGroupBase::factory( $conf ); |
274 | |
275 | return true; |
276 | } |
277 | |
278 | public static function getLanguagesInState( $banner, $state ) { |
279 | if ( !self::isUsingGroupReview() ) { |
280 | throw new LogicException( |
281 | 'CentralNotice is not using group review. Cannot query group review state.' |
282 | ); |
283 | } |
284 | |
285 | $groupName = self::getTranslateGroupName( $banner ); |
286 | |
287 | $db = CNDatabase::getDb(); |
288 | $result = $db->select( |
289 | 'translate_groupreviews', |
290 | 'tgr_lang', |
291 | [ |
292 | 'tgr_group' => $groupName, |
293 | 'tgr_state' => $state, |
294 | ], |
295 | __METHOD__ |
296 | ); |
297 | |
298 | $langs = []; |
299 | foreach ( $result as $row ) { |
300 | $langs[] = $row->tgr_lang; |
301 | } |
302 | return $langs; |
303 | } |
304 | } |