Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroupSubscription.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
6use EmptyIterator;
7use Iterator;
8use MediaWiki\Config\ServiceOptions;
9use MediaWiki\Extension\Notifications\Model\Event;
10use MediaWiki\JobQueue\JobQueueGroup;
11use MediaWiki\Title\Title;
12use MediaWiki\User\User;
13use MediaWiki\User\UserIdentity;
14use MediaWiki\User\UserIdentityLookup;
15use MessageGroup;
16use Psr\Log\LoggerInterface;
17use StatusValue;
18
26
27 private bool $isMessageGroupSubscriptionEnabled;
28 private array $queuedMessages = [];
29 private ?MockEventCreator $mockEventCreator = null;
30
31 public const STATE_ADDED = 'added';
32 public const STATE_UPDATED = 'updated';
33 public const CONSTRUCTOR_OPTIONS = [ 'TranslateEnableMessageGroupSubscription' ];
34
35 public const NOT_ENABLED = 'mgs-not-enabled';
36 public const UNNAMED_USER_UNSUPPORTED = 'mgs-unnamed-user-unsupported';
37 public const DYNAMIC_GROUP_UNSUPPORTED = 'mgs-dynamic-group-unsupported';
38
39 public function __construct(
40 private readonly MessageGroupSubscriptionStore $groupSubscriptionStore,
41 private readonly JobQueueGroup $jobQueueGroup,
42 private readonly UserIdentityLookup $userIdentityLookup,
43 private readonly LoggerInterface $logger,
44 ServiceOptions $options
45 ) {
46 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
47 $this->isMessageGroupSubscriptionEnabled = $options->get( 'TranslateEnableMessageGroupSubscription' );
48 }
49
50 public function isEnabled(): bool {
51 return $this->isMessageGroupSubscriptionEnabled;
52 }
53
54 public function subscribeToGroup( MessageGroup $group, User $user ): StatusValue {
55 $status = $this->canUserSubscribeToGroup( $group, $user );
56 if ( !$status->isOK() ) {
57 return $status;
58 }
59
60 $this->groupSubscriptionStore->addSubscription( $group->getId(), $user->getId() );
61 return StatusValue::newGood();
62 }
63
64 public function isUserSubscribedTo( MessageGroup $group, UserIdentity $user ): bool {
65 return (bool)$this->groupSubscriptionStore->getSubscriptions( [ $group->getId() ], $user->getId() );
66 }
67
68 public function unsubscribeFromGroup( MessageGroup $group, UserIdentity $user ): void {
69 $this->groupSubscriptionStore->removeSubscriptions( $group->getId(), $user->getId() );
70 }
71
72 public function unsubscribeFromGroupsById( array $groupIds, UserIdentity $user ): void {
73 $uniqueGroupIds = array_unique( $groupIds );
74 foreach ( $uniqueGroupIds as $groupId ) {
75 $this->groupSubscriptionStore->removeSubscriptions( $groupId, $user->getId() );
76 }
77 }
78
79 public function subscribeToGroupsById( array $groupIds, UserIdentity $user ): void {
80 $uniqueGroupIds = array_unique( $groupIds );
81 foreach ( $uniqueGroupIds as $groupId ) {
82 $this->groupSubscriptionStore->addSubscription( $groupId, $user->getId() );
83 }
84 }
85
87 public function getUserSubscriptions( UserIdentity $user ): array {
88 $groups = $this->groupSubscriptionStore->getSubscriptions( null, $user->getId() );
89 return array_keys( $groups );
90 }
91
98 public function queueMessage( Title $messageTitle, string $state, string $groupId ): void {
99 $this->queuedMessages[ $groupId ][ $state ][] = $messageTitle->getPrefixedDBkey();
100 }
101
102 public function queueNotificationJob(): void {
103 if ( !$this->isEnabled() || $this->queuedMessages === [] ) {
104 return;
105 }
106
107 $this->jobQueueGroup->push( MessageGroupSubscriptionNotificationJob::newJob( $this->queuedMessages ) );
108 $this->logger->debug(
109 'Queued job with changes for {countGroups} groups',
110 [ 'countGroups' => count( $this->queuedMessages ) ]
111 );
112 // Reset queued messages once job has been queued
113 $this->queuedMessages = [];
114 }
115
120 public function sendNotifications( array $changesToProcess ): void {
121 if ( !$this->isEnabled() || $changesToProcess === [] ) {
122 return;
123 }
124
125 $groupIdAggregateMapped = $this->getMappedAggregateGroupIds();
126
127 // List of changes to process along with aggregate groups.
128 $changesWithAggregateGroups = $changesToProcess;
129 $sourceGroupIdMap = [];
130 // Find aggregate groups which need to be notified.
131 foreach ( $changesToProcess as $groupId => $stateValues ) {
132 // Find the aggregate groups that the current group belongs to.
133 $aggregateGroupIds = $groupIdAggregateMapped[$groupId] ?? [];
134 if ( !$aggregateGroupIds ) {
135 continue;
136 }
137
138 foreach ( $aggregateGroupIds as $aggregateGroupId ) {
139 // The aggregate group might already be in the list of changes to process
140 $currentGroupState = $changesWithAggregateGroups[$aggregateGroupId] ??
141 $changesToProcess[$aggregateGroupId] ?? [];
142 $changesWithAggregateGroups[$aggregateGroupId] =
143 $this->appendToState( $currentGroupState, $stateValues );
144
145 // If an aggregate group is added to the list of changes directly, don't bother finding other
146 // groups that have this group as a parent and notify all subscribers; otherwise, add the source
147 // message group id due to which notification is being sent to this aggregate group.
148 if ( !isset( $changesToProcess[$aggregateGroupId] ) ) {
149 $sourceGroupIdMap[$aggregateGroupId][$groupId] = true;
150 }
151 }
152 }
153
154 $groupIdsToNotify = array_keys( $changesWithAggregateGroups );
155 $allGroupSubscribers = $this->getSubscriberIdsForGroups( $groupIdsToNotify );
156
157 // No subscribers found for the groups
158 if ( !$allGroupSubscribers ) {
159 $this->logger->info( 'No subscribers for groups.' );
160 return;
161 }
162
163 $groups = MessageGroups::getGroupsById( $groupIdsToNotify );
164 foreach ( $changesWithAggregateGroups as $groupId => $state ) {
165 $group = $groups[ $groupId ] ?? null;
166 if ( !$group ) {
167 $this->logger->debug(
168 'Group not found {groupId}.',
169 [ 'groupId' => $groupId ]
170 );
171 continue;
172 }
173
174 $groupSubscribers = $allGroupSubscribers[ $groupId ] ?? [];
175 if ( $groupSubscribers === [] ) {
176 $this->logger->info(
177 'No subscribers found for {groupId} group.',
178 [ 'groupId' => $groupId ]
179 );
180 continue;
181 }
182
183 $extraParams = [
184 'groupId' => $groupId,
185 'groupLabel' => $group->getLabel(),
186 'changes' => $state,
187 ];
188
189 if ( isset( $sourceGroupIdMap[ $groupId ] ) ) {
190 $extraParams['sourceGroupIds'] = array_unique( array_keys( $sourceGroupIdMap[ $groupId ] ) );
191 }
192
193 if ( $this->mockEventCreator ) {
194 $this->mockEventCreator->create( [
195 'type' => 'translate-mgs-message-added',
196 'extra' => $extraParams
197 ] );
198 } else {
199 Event::create( [
200 'type' => 'translate-mgs-message-added',
201 'extra' => $extraParams
202 ] );
203 }
204
205 $this->logger->info(
206 'Event created for {groupId} with {subscriberCount} subscribers.',
207 [
208 'groupId' => $groupId,
209 'subscriberCount' => count( $groupSubscribers )
210 ]
211 );
212 }
213 }
214
220 public function getGroupSubscribers( string $groupId ): Iterator {
221 $groupSubscriberIds = $this->getSubscriberIdsForGroups( [ $groupId ] );
222 $groupSubscriberIds = $groupSubscriberIds[ $groupId ] ?? [];
223 if ( $groupSubscriberIds === [] ) {
224 return new EmptyIterator();
225 }
226
227 return $this->userIdentityLookup->newSelectQueryBuilder()
228 ->whereUserIds( $groupSubscriberIds )
229 ->caller( __METHOD__ )
230 ->fetchUserIdentities();
231 }
232
239 public function getGroupSubscriberUnion( array $groupIds ): array {
240 return $this->groupSubscriptionStore->getSubscriptionByGroupUnion( $groupIds );
241 }
242
243 public function setMockEventCreator( MockEventCreator $mockEventCreator ): void {
244 $this->mockEventCreator = $mockEventCreator;
245 }
246
253 private function getSubscriberIdsForGroups( array $groupIds ): array {
254 return $this->groupSubscriptionStore->getSubscriptions( $groupIds, null );
255 }
256
257 public function canUserSubscribeToGroup( MessageGroup $group, User $user ): StatusValue {
258 if ( !$this->isEnabled() ) {
259 return StatusValue::newFatal( self::NOT_ENABLED );
260 }
261
262 if ( MessageGroups::isDynamic( $group ) ) {
263 return StatusValue::newFatal( self::DYNAMIC_GROUP_UNSUPPORTED );
264 }
265
266 if ( !$user->isNamed() ) {
267 return StatusValue::newFatal( self::UNNAMED_USER_UNSUPPORTED );
268 }
269
270 return StatusValue::newGood();
271 }
272
277 private function getMappedAggregateGroupIds(): array {
278 $groupStructure = MessageGroups::getGroupStructure();
279 // Flatten the group structure for easy indexing
280 $groupIdAggregateMapped = [];
281 foreach ( $groupStructure as $groupId => $mappedGroups ) {
282 if ( !is_array( $mappedGroups ) ) {
283 // We don't care about non-aggregate groups
284 continue;
285 }
286
287 // array_merge_recursive causes duplicates to appear in the mapped group ids, but that's
288 // alright, we can deduplicate them when we use the values.
289 $groupIdAggregateMapped = array_merge_recursive(
290 $groupIdAggregateMapped,
291 $this->mapGroups( $mappedGroups, $groupId )
292 );
293 }
294 return $groupIdAggregateMapped;
295 }
296
298 private function mapGroups( array $subGroupList, string $groupId ): array {
299 $groupIdAggregateMapped = [];
300 foreach ( $subGroupList as $subGroups ) {
301 if ( is_array( $subGroups ) && $subGroups ) {
302 // First group in the array is the aggregate group
303 $subGroupId = ( $subGroups[0] )->getId();
304 $groupIdAggregateMapped = $this->mapGroups( array_slice( $subGroups, 1 ), $subGroupId );
305 foreach ( array_keys( $groupIdAggregateMapped ) as $mappedGubGroupId ) {
306 $groupIdAggregateMapped[$mappedGubGroupId][] = $groupId;
307 }
308 $groupIdAggregateMapped[$subGroupId][] = $groupId;
309 } else {
310 $groupIdAggregateMapped[$subGroups->getId()][] = $groupId;
311 }
312 }
313 return $groupIdAggregateMapped;
314 }
315
316 private function appendToState( array $existingState, array $newState ): array {
317 foreach ( $newState as $stateType => $stateValues ) {
318 $existingState[$stateType] = array_unique(
319 array_merge( $existingState[$stateType] ?? [], $stateValues )
320 );
321 }
322
323 return $existingState;
324 }
325}
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(), $services->getContentLanguageCode() ->toString(), 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 { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} 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(), $services->get( 'Translate:HookRunner'),);}, '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->getConnectionProvider());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);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array
Store service for looking up and storing user subscriptions to message group.
Manage user subscriptions to message groups and trigger notifications.
getGroupSubscriberUnion(array $groupIds)
Return a list of users ids that belong to all the given groups.
queueMessage(Title $messageTitle, string $state, string $groupId)
Queue a message / group to send notifications for.
getGroupSubscribers(string $groupId)
Given a group id returns an iterator to the subscribers of that group.
Interface for message groups.
getId()
Returns the unique identifier for this group.
getLabel(?IContextSource $context=null)
Returns the human readable label (as plain text).