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