Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
20.56% |
96 / 467 |
|
29.63% |
8 / 27 |
CRAP | |
0.00% |
0 / 1 |
HookHandler | |
20.56% |
96 / 467 |
|
29.63% |
8 / 27 |
5113.85 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
setupTranslate | |
0.00% |
0 / 227 |
|
0.00% |
0 / 1 |
272 | |||
registerHookHandlers | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
onUserGetReservedNames | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
onAbuseFilterAlterVariables | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
onAbuseFilterComputeVariable | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
onAbuseFilterBuilder | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setupParserHooks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
onPageContentLanguage | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
translateMessageDocumentationLanguage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
searchProfile | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
2.01 | |||
searchProfileForm | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
42 | |||
searchProfileSetupEngine | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
preventCategorization | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
6.01 | |||
showFakeCategories | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
addConfig | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
onAdminLinks | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
onMergeAccountFromTo | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
onDeleteAccount | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
onAbortEmailNotificationReview | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
onTitleIsAlwaysKnown | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
onParserFirstCallInit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
translateRenderParserFunction | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
validateMessage | |
84.85% |
28 / 33 |
|
0.00% |
0 / 1 |
10.35 | |||
onRevisionRecordInserted | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
5 | |||
onListDefinedTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onChangeTagsListActive | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | declare( strict_types=1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate; |
5 | |
6 | use ALItem; |
7 | use ALTree; |
8 | use LogFormatter; |
9 | use MediaWiki\Api\ApiRawMessage; |
10 | use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook; |
11 | use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook; |
12 | use MediaWiki\Config\Config; |
13 | use MediaWiki\Config\ConfigException; |
14 | use MediaWiki\Config\ServiceOptions; |
15 | use MediaWiki\Content\Content; |
16 | use MediaWiki\Content\TextContent; |
17 | use MediaWiki\Context\IContextSource; |
18 | use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder; |
19 | use MediaWiki\Extension\Translate\LogFormatter as TranslateLogFormatter; |
20 | use MediaWiki\Extension\Translate\MessageBundleTranslation\ScribuntoHookHandler; |
21 | use MediaWiki\Extension\Translate\MessageGroupProcessing\DeleteTranslatableBundleJob; |
22 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupSubscriptionHookHandler; |
23 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupSubscriptionNotificationJob; |
24 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MoveTranslatableBundleJob; |
25 | use MediaWiki\Extension\Translate\MessageGroupProcessing\RevTagStore; |
26 | use MediaWiki\Extension\Translate\MessageGroupProcessing\TranslatableBundleLogFormatter; |
27 | use MediaWiki\Extension\Translate\MessageLoading\FatMessage; |
28 | use MediaWiki\Extension\Translate\MessageLoading\MessageHandle; |
29 | use MediaWiki\Extension\Translate\MessageLoading\RebuildMessageIndexJob; |
30 | use MediaWiki\Extension\Translate\PageTranslation\DeleteTranslatableBundleSpecialPage; |
31 | use MediaWiki\Extension\Translate\PageTranslation\Hooks; |
32 | use MediaWiki\Extension\Translate\PageTranslation\MarkForTranslationActionApi; |
33 | use MediaWiki\Extension\Translate\PageTranslation\MigrateTranslatablePageSpecialPage; |
34 | use MediaWiki\Extension\Translate\PageTranslation\PageTranslationSpecialPage; |
35 | use MediaWiki\Extension\Translate\PageTranslation\PrepareTranslatablePageSpecialPage; |
36 | use MediaWiki\Extension\Translate\PageTranslation\RenderTranslationPageJob; |
37 | use MediaWiki\Extension\Translate\PageTranslation\UpdateTranslatablePageJob; |
38 | use MediaWiki\Extension\Translate\SystemUsers\FuzzyBot; |
39 | use MediaWiki\Extension\Translate\SystemUsers\TranslateUserManager; |
40 | use MediaWiki\Extension\Translate\TranslatorInterface\TranslateEditAddons; |
41 | use MediaWiki\Extension\Translate\TranslatorSandbox\ManageTranslatorSandboxSpecialPage; |
42 | use MediaWiki\Extension\Translate\TranslatorSandbox\TranslateSandboxEmailJob; |
43 | use MediaWiki\Extension\Translate\TranslatorSandbox\TranslationStashActionApi; |
44 | use MediaWiki\Extension\Translate\TranslatorSandbox\TranslationStashSpecialPage; |
45 | use MediaWiki\Extension\Translate\TranslatorSandbox\TranslatorSandboxActionApi; |
46 | use MediaWiki\Extension\Translate\TtmServer\SearchableTtmServer; |
47 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
48 | use MediaWiki\Hook\ParserFirstCallInitHook; |
49 | use MediaWiki\Html\Html; |
50 | use MediaWiki\Language\Language; |
51 | use MediaWiki\Languages\LanguageNameUtils; |
52 | use MediaWiki\MediaWikiServices; |
53 | use MediaWiki\Output\OutputPage; |
54 | use MediaWiki\Parser\Parser; |
55 | use MediaWiki\Parser\ParserOutput; |
56 | use MediaWiki\Registration\ExtensionRegistry; |
57 | use MediaWiki\Revision\Hook\RevisionRecordInsertedHook; |
58 | use MediaWiki\Revision\RevisionLookup; |
59 | use MediaWiki\Settings\SettingsBuilder; |
60 | use MediaWiki\SpecialPage\SpecialPage; |
61 | use MediaWiki\Specials\SpecialSearch; |
62 | use MediaWiki\Status\Status; |
63 | use MediaWiki\StubObject\StubUserLang; |
64 | use MediaWiki\Title\Title; |
65 | use MediaWiki\Title\TitleValue; |
66 | use MediaWiki\User\Hook\UserGetReservedNamesHook; |
67 | use MediaWiki\User\User; |
68 | use MediaWiki\Xml\XmlSelect; |
69 | use RecentChange; |
70 | use SearchEngine; |
71 | use Wikimedia\Rdbms\ILoadBalancer; |
72 | |
73 | /** |
74 | * Hooks for Translate extension. |
75 | * Contains class with basic non-feature specific hooks. |
76 | * Main subsystems, like page translation, should have their own hook handler. * |
77 | * Most of the hooks on this class are still old style static functions, but new new hooks should |
78 | * use the new style hook handlers with interfaces. |
79 | * |
80 | * @author Niklas Laxström |
81 | * @license GPL-2.0-or-later |
82 | */ |
83 | class HookHandler implements |
84 | ChangeTagsListActiveHook, |
85 | ListDefinedTagsHook, |
86 | ParserFirstCallInitHook, |
87 | RevisionRecordInsertedHook, |
88 | UserGetReservedNamesHook |
89 | { |
90 | /** |
91 | * Any user of this list should make sure that the tables |
92 | * actually exist, since they may be optional |
93 | */ |
94 | private const USER_MERGE_TABLES = [ |
95 | 'translate_stash' => 'ts_user', |
96 | 'translate_reviews' => 'trr_user', |
97 | ]; |
98 | private RevisionLookup $revisionLookup; |
99 | private ILoadBalancer $loadBalancer; |
100 | private Config $config; |
101 | private LanguageNameUtils $languageNameUtils; |
102 | |
103 | public function __construct( |
104 | RevisionLookup $revisionLookup, |
105 | ILoadBalancer $loadBalancer, |
106 | Config $config, |
107 | LanguageNameUtils $languageNameUtils |
108 | ) { |
109 | $this->revisionLookup = $revisionLookup; |
110 | $this->loadBalancer = $loadBalancer; |
111 | $this->config = $config; |
112 | $this->languageNameUtils = $languageNameUtils; |
113 | } |
114 | |
115 | /** Do late setup that depends on configuration. */ |
116 | public static function setupTranslate(): void { |
117 | global $wgTranslateYamlLibrary, $wgLogTypes; |
118 | $hooks = []; |
119 | |
120 | /* |
121 | * Text that will be shown in translations if the translation is outdated. |
122 | * Must be something that does not conflict with actual content. |
123 | */ |
124 | if ( !defined( 'TRANSLATE_FUZZY' ) ) { |
125 | define( 'TRANSLATE_FUZZY', '!!FUZZY!!' ); |
126 | } |
127 | |
128 | $wgTranslateYamlLibrary ??= function_exists( 'yaml_parse' ) ? 'phpyaml' : 'spyc'; |
129 | |
130 | $hooks['PageSaveComplete'][] = [ TranslateEditAddons::class, 'onSaveComplete' ]; |
131 | global $wgJobClasses; |
132 | |
133 | $wgJobClasses['MessageIndexRebuildJob'] = RebuildMessageIndexJob::class; |
134 | $wgJobClasses['RebuildMessageIndexJob'] = RebuildMessageIndexJob::class; |
135 | |
136 | // Page translation setup check and init if enabled. |
137 | global $wgEnablePageTranslation; |
138 | if ( $wgEnablePageTranslation ) { |
139 | // Special page and the right to use it |
140 | global $wgSpecialPages, $wgAvailableRights; |
141 | $wgSpecialPages['PageTranslation'] = [ |
142 | 'class' => PageTranslationSpecialPage::class, |
143 | 'services' => [ |
144 | 'LanguageFactory', |
145 | 'LinkBatchFactory', |
146 | 'JobQueueGroup', |
147 | 'PermissionManager', |
148 | 'Translate:TranslatablePageMarker', |
149 | 'Translate:TranslatablePageParser', |
150 | 'Translate:MessageGroupMetadata', |
151 | 'Translate:TranslatablePageView', |
152 | 'Translate:TranslatablePageStateStore', |
153 | 'FormatterFactory' |
154 | ] |
155 | ]; |
156 | $wgSpecialPages['PageTranslationDeletePage'] = [ |
157 | 'class' => DeleteTranslatableBundleSpecialPage::class, |
158 | 'services' => [ |
159 | 'PermissionManager', |
160 | 'Translate:TranslatableBundleDeleter', |
161 | 'Translate:TranslatableBundleFactory', |
162 | ] |
163 | ]; |
164 | |
165 | // right-pagetranslation action-pagetranslation |
166 | $wgAvailableRights[] = 'pagetranslation'; |
167 | |
168 | $wgSpecialPages['PageMigration'] = MigrateTranslatablePageSpecialPage::class; |
169 | $wgSpecialPages['PagePreparation'] = PrepareTranslatablePageSpecialPage::class; |
170 | |
171 | global $wgActionFilteredLogs, $wgLogActionsHandlers; |
172 | |
173 | // log-description-pagetranslation log-name-pagetranslation logentry-pagetranslation-mark |
174 | // logentry-pagetranslation-unmark logentry-pagetranslation-moveok |
175 | // logentry-pagetranslation-movenok logentry-pagetranslation-deletefok |
176 | // logentry-pagetranslation-deletefnok logentry-pagetranslation-deletelok |
177 | // logentry-pagetranslation-deletelnok logentry-pagetranslation-encourage |
178 | // logentry-pagetranslation-discourage logentry-pagetranslation-prioritylanguages |
179 | // logentry-pagetranslation-associate logentry-pagetranslation-dissociate |
180 | if ( !in_array( 'pagetranslation', $wgLogTypes ) ) { |
181 | $wgLogTypes[] = 'pagetranslation'; |
182 | } |
183 | $wgLogActionsHandlers['pagetranslation/mark'] = TranslatableBundleLogFormatter::class; |
184 | $wgLogActionsHandlers['pagetranslation/unmark'] = TranslatableBundleLogFormatter::class; |
185 | $wgLogActionsHandlers['pagetranslation/moveok'] = TranslatableBundleLogFormatter::class; |
186 | $wgLogActionsHandlers['pagetranslation/movenok'] = TranslatableBundleLogFormatter::class; |
187 | $wgLogActionsHandlers['pagetranslation/deletelok'] = TranslatableBundleLogFormatter::class; |
188 | $wgLogActionsHandlers['pagetranslation/deletefok'] = TranslatableBundleLogFormatter::class; |
189 | $wgLogActionsHandlers['pagetranslation/deletelnok'] = TranslatableBundleLogFormatter::class; |
190 | $wgLogActionsHandlers['pagetranslation/deletefnok'] = TranslatableBundleLogFormatter::class; |
191 | $wgLogActionsHandlers['pagetranslation/encourage'] = TranslatableBundleLogFormatter::class; |
192 | $wgLogActionsHandlers['pagetranslation/discourage'] = TranslatableBundleLogFormatter::class; |
193 | $wgLogActionsHandlers['pagetranslation/prioritylanguages'] = TranslatableBundleLogFormatter::class; |
194 | $wgLogActionsHandlers['pagetranslation/associate'] = TranslatableBundleLogFormatter::class; |
195 | $wgLogActionsHandlers['pagetranslation/dissociate'] = TranslatableBundleLogFormatter::class; |
196 | $wgActionFilteredLogs['pagetranslation'] = [ |
197 | 'mark' => [ 'mark' ], |
198 | 'unmark' => [ 'unmark' ], |
199 | 'move' => [ 'moveok', 'movenok' ], |
200 | 'delete' => [ 'deletefok', 'deletefnok', 'deletelok', 'deletelnok' ], |
201 | 'encourage' => [ 'encourage' ], |
202 | 'discourage' => [ 'discourage' ], |
203 | 'prioritylanguages' => [ 'prioritylanguages' ], |
204 | 'aggregategroups' => [ 'associate', 'dissociate' ], |
205 | ]; |
206 | |
207 | if ( !in_array( 'messagebundle', $wgLogTypes ) ) { |
208 | $wgLogTypes[] = 'messagebundle'; |
209 | } |
210 | $wgLogActionsHandlers['messagebundle/moveok'] = TranslatableBundleLogFormatter::class; |
211 | $wgLogActionsHandlers['messagebundle/movenok'] = TranslatableBundleLogFormatter::class; |
212 | $wgLogActionsHandlers['messagebundle/deletefok'] = TranslatableBundleLogFormatter::class; |
213 | $wgLogActionsHandlers['messagebundle/deletefnok'] = TranslatableBundleLogFormatter::class; |
214 | $wgActionFilteredLogs['messagebundle'] = [ |
215 | 'move' => [ 'moveok', 'movenok' ], |
216 | 'delete' => [ 'deletefok', 'deletefnok' ], |
217 | ]; |
218 | |
219 | $wgLogActionsHandlers['import/translatable-bundle'] = TranslatableBundleLogFormatter::class; |
220 | |
221 | $wgJobClasses['RenderTranslationPageJob'] = RenderTranslationPageJob::class; |
222 | $wgJobClasses['NonPrioritizedRenderTranslationPageJob'] = RenderTranslationPageJob::class; |
223 | $wgJobClasses['MoveTranslatableBundleJob'] = MoveTranslatableBundleJob::class; |
224 | $wgJobClasses['DeleteTranslatableBundleJob'] = DeleteTranslatableBundleJob::class; |
225 | $wgJobClasses['UpdateTranslatablePageJob'] = UpdateTranslatablePageJob::class; |
226 | |
227 | // API modules |
228 | global $wgAPIModules; |
229 | $wgAPIModules['markfortranslation'] = [ |
230 | 'class' => MarkForTranslationActionApi::class, |
231 | 'services' => [ |
232 | 'Translate:TranslatablePageMarker', |
233 | 'Translate:MessageGroupMetadata', |
234 | ] |
235 | ]; |
236 | |
237 | // Namespaces |
238 | global $wgNamespacesWithSubpages, $wgNamespaceProtection; |
239 | global $wgTranslateMessageNamespaces; |
240 | |
241 | $wgNamespacesWithSubpages[NS_TRANSLATIONS] = true; |
242 | $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] = true; |
243 | |
244 | // Standard protection and register it for filtering |
245 | $wgNamespaceProtection[NS_TRANSLATIONS] = [ 'translate' ]; |
246 | $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS; |
247 | |
248 | /// Page translation hooks |
249 | |
250 | /// Register our CSS and metadata |
251 | $hooks['BeforePageDisplay'][] = [ Hooks::class, 'onBeforePageDisplay' ]; |
252 | |
253 | // Disable VE |
254 | $hooks['VisualEditorBeforeEditor'][] = [ Hooks::class, 'onVisualEditorBeforeEditor' ]; |
255 | |
256 | // Check syntax for \<translate> |
257 | $hooks['MultiContentSave'][] = [ Hooks::class, 'tpSyntaxCheck' ]; |
258 | $hooks['EditFilterMergedContent'][] = |
259 | [ Hooks::class, 'tpSyntaxCheckForEditContent' ]; |
260 | |
261 | // Add transtag to page props for discovery |
262 | $hooks['PageSaveComplete'][] = [ Hooks::class, 'addTranstagAfterSave' ]; |
263 | |
264 | $hooks['RevisionRecordInserted'][] = [ Hooks::class, 'updateTranstagOnNullRevisions' ]; |
265 | |
266 | // Register different ways to show language links |
267 | $hooks['ParserFirstCallInit'][] = [ self::class, 'setupParserHooks' ]; |
268 | $hooks['LanguageLinks'][] = [ Hooks::class, 'addLanguageLinks' ]; |
269 | $hooks['SkinTemplateGetLanguageLink'][] = [ Hooks::class, 'formatLanguageLink' ]; |
270 | |
271 | // Allow templates to query whether they are transcluded in a translatable/translated page |
272 | $hooks['GetMagicVariableIDs'][] = [ Hooks::class, 'onGetMagicVariableIDs' ]; |
273 | $hooks['ParserGetVariableValueSwitch'][] = [ Hooks::class, 'onParserGetVariableValueSwitch' ]; |
274 | |
275 | // Strip \<translate> tags etc. from source pages when rendering |
276 | $hooks['ParserBeforeInternalParse'][] = [ Hooks::class, 'renderTagPage' ]; |
277 | // Strip \<translate> tags etc. from source pages when preprocessing |
278 | $hooks['ParserBeforePreprocess'][] = [ Hooks::class, 'preprocessTagPage' ]; |
279 | $hooks['ParserOutputPostCacheTransform'][] = |
280 | [ Hooks::class, 'onParserOutputPostCacheTransform' ]; |
281 | |
282 | $hooks['BeforeParserFetchTemplateRevisionRecord'][] = |
283 | [ Hooks::class, 'fetchTranslatableTemplateAndTitle' ]; |
284 | |
285 | // Set the page content language |
286 | $hooks['PageContentLanguage'][] = [ Hooks::class, 'onPageContentLanguage' ]; |
287 | |
288 | // Prevent editing of certain pages in translations namespace |
289 | $hooks['getUserPermissionsErrorsExpensive'][] = |
290 | [ Hooks::class, 'onGetUserPermissionsErrorsExpensive' ]; |
291 | // Prevent editing of translation pages directly |
292 | $hooks['getUserPermissionsErrorsExpensive'][] = |
293 | [ Hooks::class, 'preventDirectEditing' ]; |
294 | |
295 | // Our custom header for translation pages |
296 | $hooks['ArticleViewHeader'][] = [ Hooks::class, 'translatablePageHeader' ]; |
297 | |
298 | // Edit notice shown on translatable pages |
299 | $hooks['TitleGetEditNotices'][] = [ Hooks::class, 'onTitleGetEditNotices' ]; |
300 | |
301 | // Custom move page that can move all the associated pages too |
302 | $hooks['SpecialPage_initList'][] = [ Hooks::class, 'replaceMovePage' ]; |
303 | // Locking during page moves |
304 | $hooks['getUserPermissionsErrorsExpensive'][] = |
305 | [ Hooks::class, 'lockedPagesCheck' ]; |
306 | // Disable action=delete |
307 | $hooks['ArticleConfirmDelete'][] = [ Hooks::class, 'disableDelete' ]; |
308 | |
309 | // Replace subpage logic behavior |
310 | $hooks['SkinSubPageSubtitle'][] = [ Hooks::class, 'replaceSubtitle' ]; |
311 | |
312 | // Replaced edit tab with translation tab for translation pages |
313 | $hooks['SkinTemplateNavigation::Universal'][] = [ Hooks::class, 'translateTab' ]; |
314 | |
315 | // Update translated page when translation unit is moved |
316 | $hooks['PageMoveComplete'][] = [ Hooks::class, 'onMovePageTranslationUnits' ]; |
317 | |
318 | // Update translated page when translation unit is deleted |
319 | $hooks['ArticleDeleteComplete'][] = [ Hooks::class, 'onDeleteTranslationUnit' ]; |
320 | |
321 | // Prevent editing of translation pages. |
322 | $hooks['ReplaceTextFilterPageTitlesForEdit'][] = [ Hooks::class, 'onReplaceTextFilterPageTitlesForEdit' ]; |
323 | // Prevent renaming of translatable pages and their translation and translation units |
324 | $hooks['ReplaceTextFilterPageTitlesForRename'][] = |
325 | [ Hooks::class, 'onReplaceTextFilterPageTitlesForRename' ]; |
326 | |
327 | // Auto-create translated categories when not empty |
328 | $hooks['LinksUpdateComplete'][] = [ Hooks::class, 'onLinksUpdateComplete' ]; |
329 | } |
330 | |
331 | global $wgTranslateUseSandbox; |
332 | if ( $wgTranslateUseSandbox ) { |
333 | global $wgSpecialPages, $wgAvailableRights, $wgDefaultUserOptions; |
334 | |
335 | $wgSpecialPages['ManageTranslatorSandbox'] = [ |
336 | 'class' => ManageTranslatorSandboxSpecialPage::class, |
337 | 'services' => [ |
338 | 'Translate:TranslationStashReader', |
339 | 'UserOptionsLookup', |
340 | 'Translate:TranslateSandbox', |
341 | ], |
342 | 'args' => [ |
343 | static function () { |
344 | return new ServiceOptions( |
345 | ManageTranslatorSandboxSpecialPage::CONSTRUCTOR_OPTIONS, |
346 | MediaWikiServices::getInstance()->getMainConfig() |
347 | ); |
348 | } |
349 | ] |
350 | ]; |
351 | $wgSpecialPages['TranslationStash'] = [ |
352 | 'class' => TranslationStashSpecialPage::class, |
353 | 'services' => [ |
354 | 'LanguageNameUtils', |
355 | 'Translate:TranslationStashReader', |
356 | 'UserOptionsLookup', |
357 | 'LanguageFactory', |
358 | ], |
359 | 'args' => [ |
360 | static function () { |
361 | return new ServiceOptions( |
362 | TranslationStashSpecialPage::CONSTRUCTOR_OPTIONS, |
363 | MediaWikiServices::getInstance()->getMainConfig() |
364 | ); |
365 | } |
366 | ] |
367 | ]; |
368 | $wgDefaultUserOptions['translate-sandbox'] = ''; |
369 | // right-translate-sandboxmanage action-translate-sandboxmanage |
370 | $wgAvailableRights[] = 'translate-sandboxmanage'; |
371 | |
372 | global $wgLogTypes, $wgLogActionsHandlers; |
373 | // log-name-translatorsandbox log-description-translatorsandbox |
374 | if ( !in_array( 'translatorsandbox', $wgLogTypes ) ) { |
375 | $wgLogTypes[] = 'translatorsandbox'; |
376 | } |
377 | // logentry-translatorsandbox-promoted logentry-translatorsandbox-rejected |
378 | $wgLogActionsHandlers['translatorsandbox/promoted'] = TranslateLogFormatter::class; |
379 | $wgLogActionsHandlers['translatorsandbox/rejected'] = TranslateLogFormatter::class; |
380 | |
381 | // This is no longer used for new entries since 2016.07. |
382 | // logentry-newusers-tsbpromoted |
383 | $wgLogActionsHandlers['newusers/tsbpromoted'] = LogFormatter::class; |
384 | |
385 | $wgJobClasses['TranslateSandboxEmailJob'] = TranslateSandboxEmailJob::class; |
386 | |
387 | global $wgAPIModules; |
388 | $wgAPIModules['translationstash'] = [ |
389 | 'class' => TranslationStashActionApi::class, |
390 | 'services' => [ |
391 | 'DBLoadBalancerFactory', |
392 | 'UserFactory', |
393 | 'Translate:MessageIndex' |
394 | ] |
395 | ]; |
396 | $wgAPIModules['translatesandbox'] = [ |
397 | 'class' => TranslatorSandboxActionApi::class, |
398 | 'services' => [ |
399 | 'UserFactory', |
400 | 'UserNameUtils', |
401 | 'UserOptionsManager', |
402 | 'WikiPageFactory', |
403 | 'UserOptionsLookup', |
404 | 'Translate:TranslateSandbox', |
405 | ], |
406 | 'args' => [ |
407 | static function () { |
408 | return new ServiceOptions( |
409 | TranslatorSandboxActionApi::CONSTRUCTOR_OPTIONS, |
410 | MediaWikiServices::getInstance()->getMainConfig() |
411 | ); |
412 | } |
413 | ] |
414 | ]; |
415 | } |
416 | |
417 | global $wgNamespaceRobotPolicies; |
418 | $wgNamespaceRobotPolicies[NS_TRANSLATIONS] = 'noindex'; |
419 | |
420 | // If no service has been configured, we use a built-in fallback. |
421 | global $wgTranslateTranslationDefaultService, |
422 | $wgTranslateTranslationServices; |
423 | if ( $wgTranslateTranslationDefaultService === true ) { |
424 | $wgTranslateTranslationDefaultService = 'TTMServer'; |
425 | if ( !isset( $wgTranslateTranslationServices['TTMServer'] ) ) { |
426 | $wgTranslateTranslationServices['TTMServer'] = [ |
427 | 'database' => false, |
428 | 'cutoff' => 0.75, |
429 | 'type' => 'ttmserver', |
430 | 'public' => false, |
431 | ]; |
432 | } |
433 | } |
434 | |
435 | global $wgTranslateEnableMessageGroupSubscription; |
436 | if ( $wgTranslateEnableMessageGroupSubscription ) { |
437 | if ( !ExtensionRegistry::getInstance()->isLoaded( 'Echo' ) ) { |
438 | throw new ConfigException( |
439 | 'Translate: Message group subscriptions (TranslateEnableMessageGroupSubscription) are ' . |
440 | 'enabled but Echo extension is not installed' |
441 | ); |
442 | } |
443 | MessageGroupSubscriptionHookHandler::registerHooks( $hooks ); |
444 | $wgJobClasses['MessageGroupSubscriptionNotificationJob'] = MessageGroupSubscriptionNotificationJob::class; |
445 | } |
446 | |
447 | global $wgTranslateEnableEventLogging; |
448 | if ( $wgTranslateEnableEventLogging ) { |
449 | if ( !ExtensionRegistry::getInstance()->isLoaded( 'EventLogging' ) ) { |
450 | throw new ConfigException( |
451 | 'Translate: Event logging (TranslateEnableEventLogging) is ' . |
452 | 'enabled but EventLogging extension is not installed' |
453 | ); |
454 | } |
455 | } |
456 | |
457 | global $wgTranslateEnableLuaIntegration; |
458 | if ( $wgTranslateEnableLuaIntegration ) { |
459 | if ( ExtensionRegistry::getInstance()->isLoaded( 'Scribunto' ) ) { |
460 | $hooks[ 'ScribuntoExternalLibraries' ][] = static function ( string $engine, array &$extraLibraries ) { |
461 | $scribuntoHookHandler = new ScribuntoHookHandler(); |
462 | $scribuntoHookHandler->onScribuntoExternalLibraries( $engine, $extraLibraries ); |
463 | }; |
464 | } else { |
465 | wfLogWarning( |
466 | 'Translate: Lua integration (TranslateEnableLuaIntegration) is ' . |
467 | 'enabled but Scribunto extension is not installed' |
468 | ); |
469 | } |
470 | } |
471 | |
472 | static::registerHookHandlers( $hooks ); |
473 | } |
474 | |
475 | private static function registerHookHandlers( array $hooks ): void { |
476 | if ( defined( 'MW_PHPUNIT_TEST' ) && MediaWikiServices::hasInstance() ) { |
477 | // When called from a test case's setUp() method, |
478 | // we can use HookContainer, but we cannot use SettingsBuilder. |
479 | $hookContainer = MediaWikiServices::getInstance()->getHookContainer(); |
480 | foreach ( $hooks as $name => $handlers ) { |
481 | foreach ( $handlers as $h ) { |
482 | $hookContainer->register( $name, $h ); |
483 | } |
484 | } |
485 | } else { |
486 | $settingsBuilder = SettingsBuilder::getInstance(); |
487 | $settingsBuilder->registerHookHandlers( $hooks ); |
488 | } |
489 | } |
490 | |
491 | /** |
492 | * Prevents anyone from registering or logging in as FuzzyBot |
493 | * @inheritDoc |
494 | */ |
495 | public function onUserGetReservedNames( &$reservedUsernames ): void { |
496 | $reservedUsernames[] = FuzzyBot::getName(); |
497 | $reservedUsernames[] = TranslateUserManager::getName(); |
498 | } |
499 | |
500 | /** Used for setting an AbuseFilter variable. */ |
501 | public static function onAbuseFilterAlterVariables( |
502 | VariableHolder &$vars, Title $title, User $user |
503 | ): void { |
504 | $handle = new MessageHandle( $title ); |
505 | |
506 | // Only set this variable if we are in a proper namespace to avoid |
507 | // unnecessary overhead in non-translation pages |
508 | if ( $handle->isMessageNamespace() ) { |
509 | $vars->setLazyLoadVar( |
510 | 'translate_source_text', |
511 | 'translate-get-source', |
512 | [ 'handle' => $handle ] |
513 | ); |
514 | $vars->setLazyLoadVar( |
515 | 'translate_target_language', |
516 | 'translate-get-target-language', |
517 | [ 'handle' => $handle ] |
518 | ); |
519 | } |
520 | } |
521 | |
522 | /** Computes the translate_source_text and translate_target_language AbuseFilter variables */ |
523 | public static function onAbuseFilterComputeVariable( |
524 | string $method, |
525 | VariableHolder $vars, |
526 | array $parameters, |
527 | ?string &$result |
528 | ): bool { |
529 | if ( $method !== 'translate-get-source' && $method !== 'translate-get-target-language' ) { |
530 | return true; |
531 | } |
532 | |
533 | $handle = $parameters['handle']; |
534 | $value = ''; |
535 | if ( $handle->isValid() ) { |
536 | if ( $method === 'translate-get-source' ) { |
537 | $group = $handle->getGroup(); |
538 | $value = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() ); |
539 | } else { |
540 | $value = $handle->getCode(); |
541 | } |
542 | } |
543 | |
544 | $result = $value; |
545 | |
546 | return false; |
547 | } |
548 | |
549 | /** Register AbuseFilter variables provided by Translate. */ |
550 | public static function onAbuseFilterBuilder( array &$builderValues ): void { |
551 | // The following messages are generated here: |
552 | // * abusefilter-edit-builder-vars-translate-source-text |
553 | // * abusefilter-edit-builder-vars-translate-target-language |
554 | $builderValues['vars']['translate_source_text'] = 'translate-source-text'; |
555 | $builderValues['vars']['translate_target_language'] = 'translate-target-language'; |
556 | } |
557 | |
558 | /** |
559 | * Hook: ParserFirstCallInit |
560 | * Registers \<languages> tag with the parser. |
561 | */ |
562 | public static function setupParserHooks( Parser $parser ): void { |
563 | // For nice language list in-page |
564 | $parser->setHook( 'languages', [ Hooks::class, 'languages' ] ); |
565 | } |
566 | |
567 | /** |
568 | * Hook: PageContentLanguage |
569 | * Set the correct page content language for translation units. |
570 | * @param Title $title |
571 | * @param Language|StubUserLang|string &$pageLang |
572 | */ |
573 | public static function onPageContentLanguage( Title $title, &$pageLang ): void { |
574 | $handle = new MessageHandle( $title ); |
575 | if ( $handle->isMessageNamespace() ) { |
576 | $pageLang = $handle->getEffectiveLanguage(); |
577 | } |
578 | } |
579 | |
580 | /** |
581 | * Hook: LanguageGetTranslatedLanguageNames |
582 | * Hook: TranslateSupportedLanguages |
583 | */ |
584 | public static function translateMessageDocumentationLanguage( array &$names, ?string $code ): void { |
585 | global $wgTranslateDocumentationLanguageCode; |
586 | if ( $wgTranslateDocumentationLanguageCode ) { |
587 | // Special case the autonyms |
588 | if ( |
589 | $wgTranslateDocumentationLanguageCode === $code || |
590 | $code === null |
591 | ) { |
592 | $code = 'en'; |
593 | } |
594 | |
595 | $names[$wgTranslateDocumentationLanguageCode] = |
596 | wfMessage( 'translate-documentation-language' )->inLanguage( $code )->plain(); |
597 | } |
598 | } |
599 | |
600 | /** Hook: SpecialSearchProfiles */ |
601 | public static function searchProfile( array &$profiles ): void { |
602 | global $wgTranslateMessageNamespaces; |
603 | $insert = []; |
604 | $insert['translation'] = [ |
605 | 'message' => 'translate-searchprofile', |
606 | 'tooltip' => 'translate-searchprofile-tooltip', |
607 | 'namespaces' => $wgTranslateMessageNamespaces, |
608 | ]; |
609 | |
610 | // Insert translations before 'all' |
611 | $index = array_search( 'all', array_keys( $profiles ) ); |
612 | |
613 | // Or just at the end if all is not found |
614 | if ( $index === false ) { |
615 | wfWarn( '"all" not found in search profiles' ); |
616 | $index = count( $profiles ); |
617 | } |
618 | |
619 | $profiles = array_merge( |
620 | array_slice( $profiles, 0, $index ), |
621 | $insert, |
622 | array_slice( $profiles, $index ) |
623 | ); |
624 | } |
625 | |
626 | /** Hook: SpecialSearchProfileForm */ |
627 | public static function searchProfileForm( |
628 | SpecialSearch $search, |
629 | string &$form, |
630 | string $profile, |
631 | string $term, |
632 | array $opts |
633 | ): void { |
634 | if ( $profile !== 'translation' ) { |
635 | return; |
636 | } |
637 | |
638 | if ( Services::getInstance()->getTtmServerFactory()->getDefaultForQuerying() instanceof SearchableTtmServer ) { |
639 | $href = SpecialPage::getTitleFor( 'SearchTranslations' ) |
640 | ->getFullUrl( [ 'query' => $term ] ); |
641 | $form = Html::successBox( |
642 | $search->msg( 'translate-searchprofile-note', $href )->parse(), |
643 | 'plainlinks' |
644 | ); |
645 | |
646 | return; |
647 | } |
648 | |
649 | if ( !$search->getSearchEngine()->supports( 'title-suffix-filter' ) ) { |
650 | return; |
651 | } |
652 | |
653 | $hidden = ''; |
654 | foreach ( $opts as $key => $value ) { |
655 | $hidden .= Html::hidden( $key, $value ); |
656 | } |
657 | |
658 | $context = $search->getContext(); |
659 | $code = $context->getLanguage()->getCode(); |
660 | $selected = $context->getRequest()->getVal( 'languagefilter' ); |
661 | |
662 | $languages = Utilities::getLanguageNames( $code ); |
663 | ksort( $languages ); |
664 | |
665 | $selector = new XmlSelect( 'languagefilter', 'languagefilter' ); |
666 | $selector->setDefault( $selected ); |
667 | $selector->addOption( wfMessage( 'translate-search-nofilter' )->text(), '-' ); |
668 | foreach ( $languages as $code => $name ) { |
669 | $selector->addOption( "$code - $name", $code ); |
670 | } |
671 | |
672 | $selector = $selector->getHTML(); |
673 | |
674 | $label = Html::label( |
675 | wfMessage( 'translate-search-languagefilter' )->text(), |
676 | 'languagefilter' |
677 | ) . "\u{00A0}"; |
678 | |
679 | $form .= Html::rawElement( |
680 | 'fieldset', |
681 | [ 'id' => 'mw-searchoptions' ], |
682 | $hidden . $label . $selector |
683 | ); |
684 | } |
685 | |
686 | /** Hook: SpecialSearchSetupEngine */ |
687 | public static function searchProfileSetupEngine( |
688 | SpecialSearch $search, |
689 | string $profile, |
690 | SearchEngine $engine |
691 | ): void { |
692 | if ( $profile !== 'translation' ) { |
693 | return; |
694 | } |
695 | |
696 | $context = $search->getContext(); |
697 | $selected = $context->getRequest()->getVal( 'languagefilter' ); |
698 | if ( $selected !== '-' && $selected ) { |
699 | $engine->setFeatureData( 'title-suffix-filter', "/$selected" ); |
700 | $search->setExtraParam( 'languagefilter', $selected ); |
701 | } |
702 | } |
703 | |
704 | /** Hook: ParserAfterTidy */ |
705 | public static function preventCategorization( Parser $parser, string &$html ): void { |
706 | if ( $parser->getOptions()->getInterfaceMessage() ) { |
707 | return; |
708 | } |
709 | $pageReference = $parser->getPage(); |
710 | if ( !$pageReference ) { |
711 | return; |
712 | } |
713 | |
714 | $linkTarget = TitleValue::newFromPage( $pageReference ); |
715 | $handle = new MessageHandle( $linkTarget ); |
716 | if ( $handle->isMessageNamespace() && !$handle->isDoc() ) { |
717 | $parserOutput = $parser->getOutput(); |
718 | $names = $parserOutput->getCategoryNames(); |
719 | $parserCategories = []; |
720 | foreach ( $names as $name ) { |
721 | $parserCategories[$name] = $parserOutput->getCategorySortKey( $name ); |
722 | } |
723 | $parserOutput->setExtensionData( 'translate-fake-categories', $parserCategories ); |
724 | $parserOutput->setCategories( [] ); |
725 | } |
726 | } |
727 | |
728 | /** Hook: OutputPageParserOutput */ |
729 | public static function showFakeCategories( OutputPage $outputPage, ParserOutput $parserOutput ): void { |
730 | $fakeCategories = $parserOutput->getExtensionData( 'translate-fake-categories' ); |
731 | if ( $fakeCategories ) { |
732 | $outputPage->addCategoryLinks( $fakeCategories ); |
733 | } |
734 | } |
735 | |
736 | /** |
737 | * Hook: MakeGlobalVariablesScript |
738 | * Adds $wgTranslateDocumentationLanguageCode to ResourceLoader configuration |
739 | * when Special:Translate is shown. |
740 | */ |
741 | public static function addConfig( array &$vars, OutputPage $out ): void { |
742 | global $wgTranslateDocumentationLanguageCode, |
743 | $wgTranslatePermissionUrl, |
744 | $wgTranslateUseSandbox; |
745 | |
746 | $title = $out->getTitle(); |
747 | if ( $title->isSpecial( 'Translate' ) || |
748 | $title->isSpecial( 'TranslationStash' ) || |
749 | $title->isSpecial( 'SearchTranslations' ) |
750 | ) { |
751 | $user = $out->getUser(); |
752 | $vars['TranslateRight'] = $user->isAllowed( 'translate' ); |
753 | $vars['TranslateMessageReviewRight'] = $user->isAllowed( 'translate-messagereview' ); |
754 | $vars['DeleteRight'] = $user->isAllowed( 'delete' ); |
755 | $vars['TranslateManageRight'] = $user->isAllowed( 'translate-manage' ); |
756 | $vars['wgTranslateDocumentationLanguageCode'] = $wgTranslateDocumentationLanguageCode; |
757 | $vars['wgTranslatePermissionUrl'] = $wgTranslatePermissionUrl; |
758 | $vars['wgTranslateUseSandbox'] = $wgTranslateUseSandbox; |
759 | } |
760 | } |
761 | |
762 | /** Hook: AdminLinks */ |
763 | public static function onAdminLinks( ALTree $tree ): void { |
764 | global $wgTranslateUseSandbox; |
765 | |
766 | if ( $wgTranslateUseSandbox ) { |
767 | $sectionLabel = wfMessage( 'adminlinks_users' )->text(); |
768 | $row = $tree->getSection( $sectionLabel )->getRow( 'main' ); |
769 | $row->addItem( ALItem::newFromSpecialPage( 'TranslateSandbox' ) ); |
770 | } |
771 | } |
772 | |
773 | /** |
774 | * Hook: MergeAccountFromTo |
775 | * For UserMerge extension. |
776 | */ |
777 | public static function onMergeAccountFromTo( User $oldUser, User $newUser ): void { |
778 | $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY ); |
779 | |
780 | // Update the non-duplicate rows, we'll just delete |
781 | // the duplicate ones later |
782 | foreach ( self::USER_MERGE_TABLES as $table => $field ) { |
783 | if ( $dbw->tableExists( $table, __METHOD__ ) ) { |
784 | $dbw->newUpdateQueryBuilder() |
785 | ->update( $table ) |
786 | ->ignore() |
787 | ->set( [ $field => $newUser->getId() ] ) |
788 | ->where( [ $field => $oldUser->getId() ] ) |
789 | ->caller( __METHOD__ ) |
790 | ->execute(); |
791 | } |
792 | } |
793 | } |
794 | |
795 | /** |
796 | * Hook: DeleteAccount |
797 | * For UserMerge extension. |
798 | */ |
799 | public static function onDeleteAccount( User $oldUser ): void { |
800 | $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY ); |
801 | |
802 | // Delete any remaining rows that didn't get merged |
803 | foreach ( self::USER_MERGE_TABLES as $table => $field ) { |
804 | if ( $dbw->tableExists( $table, __METHOD__ ) ) { |
805 | $dbw->newDeleteQueryBuilder() |
806 | ->deleteFrom( $table ) |
807 | ->where( [ $field => $oldUser->getId() ] ) |
808 | ->caller( __METHOD__ ) |
809 | ->execute(); |
810 | } |
811 | } |
812 | } |
813 | |
814 | /** Hook: AbortEmailNotification */ |
815 | public static function onAbortEmailNotificationReview( |
816 | User $editor, |
817 | Title $title, |
818 | RecentChange $rc |
819 | ): bool { |
820 | return $rc->getAttribute( 'rc_log_type' ) !== 'translationreview'; |
821 | } |
822 | |
823 | /** |
824 | * Hook: TitleIsAlwaysKnown |
825 | * Make Special:MyLanguage links red if the target page doesn't exist. |
826 | * A bit hacky because the core code is not so flexible. |
827 | * @param Title $target Title object that is being checked |
828 | * @param bool|null &$isKnown Whether MediaWiki currently thinks this page is known |
829 | * @return bool True or no return value to continue or false to abort |
830 | */ |
831 | public static function onTitleIsAlwaysKnown( $target, &$isKnown ): bool { |
832 | if ( !$target->inNamespace( NS_SPECIAL ) ) { |
833 | return true; |
834 | } |
835 | |
836 | [ $name, $subpage ] = MediaWikiServices::getInstance() |
837 | ->getSpecialPageFactory()->resolveAlias( $target->getDBkey() ); |
838 | if ( $name !== 'MyLanguage' || (string)$subpage === '' ) { |
839 | return true; |
840 | } |
841 | |
842 | $realTarget = Title::newFromText( $subpage ); |
843 | if ( !$realTarget || !$realTarget->exists() ) { |
844 | $isKnown = false; |
845 | |
846 | return false; |
847 | } |
848 | |
849 | return true; |
850 | } |
851 | |
852 | public function onParserFirstCallInit( $parser ) { |
853 | $parser->setFunctionHook( 'translation', [ $this, 'translateRenderParserFunction' ] ); |
854 | } |
855 | |
856 | public function translateRenderParserFunction( Parser $parser ): string { |
857 | if ( $parser->getOptions()->getInterfaceMessage() ) { |
858 | return ''; |
859 | } |
860 | $pageReference = $parser->getPage(); |
861 | if ( !$pageReference ) { |
862 | return ''; |
863 | } |
864 | $linkTarget = TitleValue::newFromPage( $pageReference ); |
865 | $handle = new MessageHandle( $linkTarget ); |
866 | $code = $handle->getCode(); |
867 | if ( $this->languageNameUtils->isKnownLanguageTag( $code ) ) { |
868 | return '/' . $code; |
869 | } |
870 | return ''; |
871 | } |
872 | |
873 | /** |
874 | * Runs the configured validator to ensure that the message meets the required criteria. |
875 | * Hook: EditFilterMergedContent |
876 | * @return bool true if message is valid, false otherwise. |
877 | */ |
878 | public static function validateMessage( |
879 | IContextSource $context, |
880 | Content $content, |
881 | Status $status, |
882 | string $summary, |
883 | User $user |
884 | ): bool { |
885 | if ( !$content instanceof TextContent ) { |
886 | // Not interested |
887 | return true; |
888 | } |
889 | |
890 | $text = $content->getText(); |
891 | $title = $context->getTitle(); |
892 | $handle = new MessageHandle( $title ); |
893 | |
894 | if ( !$handle->isValid() ) { |
895 | return true; |
896 | } |
897 | |
898 | // Don't bother validating if FuzzyBot or translation admin are saving. |
899 | if ( $user->isAllowed( 'translate-manage' ) || $user->equals( FuzzyBot::getUser() ) ) { |
900 | return true; |
901 | } |
902 | |
903 | // Check the namespace, and perform validations for all messages excluding documentation. |
904 | if ( $handle->isMessageNamespace() && !$handle->isDoc() ) { |
905 | $group = $handle->getGroup(); |
906 | |
907 | if ( method_exists( $group, 'getMessageContent' ) ) { |
908 | // @phan-suppress-next-line PhanUndeclaredMethod |
909 | $definition = $group->getMessageContent( $handle ); |
910 | } else { |
911 | $definition = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() ); |
912 | } |
913 | |
914 | $message = new FatMessage( $handle->getKey(), $definition ); |
915 | $message->setTranslation( $text ); |
916 | |
917 | $messageValidator = $group->getValidator(); |
918 | if ( !$messageValidator ) { |
919 | return true; |
920 | } |
921 | |
922 | $validationResponse = $messageValidator->validateMessage( $message, $handle->getCode() ); |
923 | if ( $validationResponse->hasErrors() ) { |
924 | $status->fatal( new ApiRawMessage( |
925 | $context->msg( 'translate-syntax-error' )->parse(), |
926 | 'translate-validation-failed', |
927 | [ |
928 | 'validation' => [ |
929 | 'errors' => $validationResponse->getDescriptiveErrors( $context ), |
930 | 'warnings' => $validationResponse->getDescriptiveWarnings( $context ) |
931 | ] |
932 | ] |
933 | ) ); |
934 | return false; |
935 | } |
936 | } |
937 | |
938 | return true; |
939 | } |
940 | |
941 | /** @inheritDoc */ |
942 | public function onRevisionRecordInserted( $revisionRecord ): void { |
943 | $parentId = $revisionRecord->getParentId(); |
944 | if ( $parentId === 0 || $parentId === null ) { |
945 | // No parent, bail out. |
946 | return; |
947 | } |
948 | |
949 | $prevRev = $this->revisionLookup->getRevisionById( $parentId ); |
950 | if ( !$prevRev || !$revisionRecord->hasSameContent( $prevRev ) ) { |
951 | // Not a null revision, bail out. |
952 | return; |
953 | } |
954 | |
955 | // List of tags that should be copied over when updating |
956 | // tp:tag and tp:mark handling is in Hooks::updateTranstagOnNullRevisions. |
957 | $tagsToCopy = [ RevTagStore::FUZZY_TAG, RevTagStore::TRANSVER_PROP ]; |
958 | |
959 | $db = $this->loadBalancer->getConnection( DB_PRIMARY ); |
960 | $db->insertSelect( |
961 | 'revtag', |
962 | 'revtag', |
963 | [ |
964 | 'rt_type' => 'rt_type', |
965 | 'rt_page' => 'rt_page', |
966 | 'rt_revision' => $revisionRecord->getId(), |
967 | 'rt_value' => 'rt_value', |
968 | |
969 | ], |
970 | [ |
971 | 'rt_type' => $tagsToCopy, |
972 | 'rt_revision' => $parentId, |
973 | ], |
974 | __METHOD__ |
975 | ); |
976 | } |
977 | |
978 | /** @inheritDoc */ |
979 | public function onListDefinedTags( &$tags ): void { |
980 | $tags[] = 'translate-translation-pages'; |
981 | } |
982 | |
983 | /** @inheritDoc */ |
984 | public function onChangeTagsListActive( &$tags ): void { |
985 | if ( $this->config->get( 'EnablePageTranslation' ) ) { |
986 | $tags[] = 'translate-translation-pages'; |
987 | } |
988 | } |
989 | } |