Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
HookHandler.php
1<?php
2declare( strict_types=1 );
3
4namespace MediaWiki\Extension\Translate;
5
6use ALItem;
7use ALTree;
8use LogFormatter;
9use MediaWiki\Api\ApiRawMessage;
10use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
11use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
12use MediaWiki\Config\Config;
13use MediaWiki\Config\ConfigException;
14use MediaWiki\Config\ServiceOptions;
15use MediaWiki\Content\Content;
16use MediaWiki\Content\TextContent;
17use MediaWiki\Context\IContextSource;
18use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
19use MediaWiki\Extension\Translate\LogFormatter as TranslateLogFormatter;
48use MediaWiki\Hook\ParserFirstCallInitHook;
49use MediaWiki\Html\Html;
50use MediaWiki\Language\Language;
51use MediaWiki\Languages\LanguageNameUtils;
52use MediaWiki\MediaWikiServices;
53use MediaWiki\Output\OutputPage;
54use MediaWiki\Parser\Parser;
55use MediaWiki\Parser\ParserOutput;
56use MediaWiki\Registration\ExtensionRegistry;
57use MediaWiki\Revision\Hook\RevisionRecordInsertedHook;
58use MediaWiki\Revision\RevisionLookup;
59use MediaWiki\Settings\SettingsBuilder;
60use MediaWiki\SpecialPage\SpecialPage;
61use MediaWiki\Specials\SpecialSearch;
62use MediaWiki\Status\Status;
63use MediaWiki\StubObject\StubUserLang;
64use MediaWiki\Title\Title;
65use MediaWiki\Title\TitleValue;
66use MediaWiki\User\Hook\UserGetReservedNamesHook;
67use MediaWiki\User\User;
68use MediaWiki\Xml\XmlSelect;
69use RecentChange;
70use SearchEngine;
71use Wikimedia\Rdbms\ILoadBalancer;
72
83class HookHandler implements
84 ChangeTagsListActiveHook,
85 ListDefinedTagsHook,
86 ParserFirstCallInitHook,
87 RevisionRecordInsertedHook,
88 UserGetReservedNamesHook
89{
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
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
249
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
495 public function onUserGetReservedNames( &$reservedUsernames ): void {
496 $reservedUsernames[] = FuzzyBot::getName();
497 $reservedUsernames[] = TranslateUserManager::getName();
498 }
499
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
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
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
562 public static function setupParserHooks( Parser $parser ): void {
563 // For nice language list in-page
564 $parser->setHook( 'languages', [ Hooks::class, 'languages' ] );
565 }
566
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
979 public function onListDefinedTags( &$tags ): void {
980 $tags[] = 'translate-translation-pages';
981 }
982
984 public function onChangeTagsListActive( &$tags ): void {
985 if ( $this->config->get( 'EnablePageTranslation' ) ) {
986 $tags[] = 'translate-translation-pages';
987 }
988 }
989}
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):MessageGroupSubscriptionHookHandler { return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory());}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
Hooks for Translate extension.
static onAbuseFilterAlterVariables(VariableHolder &$vars, Title $title, User $user)
Used for setting an AbuseFilter variable.
onUserGetReservedNames(&$reservedUsernames)
Prevents anyone from registering or logging in as FuzzyBot @inheritDoc.
static onPageContentLanguage(Title $title, &$pageLang)
Hook: PageContentLanguage Set the correct page content language for translation units.
static onAbuseFilterBuilder(array &$builderValues)
Register AbuseFilter variables provided by Translate.
static validateMessage(IContextSource $context, Content $content, Status $status, string $summary, User $user)
Runs the configured validator to ensure that the message meets the required criteria.
static searchProfileForm(SpecialSearch $search, string &$form, string $profile, string $term, array $opts)
Hook: SpecialSearchProfileForm.
static setupTranslate()
Do late setup that depends on configuration.
static onAbortEmailNotificationReview(User $editor, Title $title, RecentChange $rc)
Hook: AbortEmailNotification.
static onAbuseFilterComputeVariable(string $method, VariableHolder $vars, array $parameters, ?string &$result)
Computes the translate_source_text and translate_target_language AbuseFilter variables.
onRevisionRecordInserted( $revisionRecord)
@inheritDoc
static addConfig(array &$vars, OutputPage $out)
Hook: MakeGlobalVariablesScript Adds $wgTranslateDocumentationLanguageCode to ResourceLoader configur...
static onAdminLinks(ALTree $tree)
Hook: AdminLinks.
static preventCategorization(Parser $parser, string &$html)
Hook: ParserAfterTidy.
static setupParserHooks(Parser $parser)
Hook: ParserFirstCallInit Registers <languages> tag with the parser.
static searchProfile(array &$profiles)
Hook: SpecialSearchProfiles.
static showFakeCategories(OutputPage $outputPage, ParserOutput $parserOutput)
Hook: OutputPageParserOutput.
static onTitleIsAlwaysKnown( $target, &$isKnown)
Hook: TitleIsAlwaysKnown Make Special:MyLanguage links red if the target page doesn't exist.
static searchProfileSetupEngine(SpecialSearch $search, string $profile, SearchEngine $engine)
Hook: SpecialSearchSetupEngine.
static onMergeAccountFromTo(User $oldUser, User $newUser)
Hook: MergeAccountFromTo For UserMerge extension.
static onDeleteAccount(User $oldUser)
Hook: DeleteAccount For UserMerge extension.
static translateMessageDocumentationLanguage(array &$names, ?string $code)
Hook: LanguageGetTranslatedLanguageNames Hook: TranslateSupportedLanguages.
Class for formatting Translate logs.
Class to manage revision tags for translatable bundles.
Message object where you can directly set the translation.
Class for pointing to messages, like Title class is for titles.
getEffectiveLanguage()
Return the Language object for the assumed language of the content, which might be different from the...
Special page which enables deleting translations of translatable bundles and translation pages.
Contains code for Special:PageMigration to migrate to page transation.
A special page for marking revisions of pages for translation.
Job for updating translation pages when translation or template changes.
Job for updating translation units and translation pages when a translatable page is marked for trans...
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Various editing enhancements to the edit page interface.
WebAPI module for storing translations for users who are in a sandbox.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
Interface for TtmServer that can act as backend for translation search.