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 JobQueueGroup;
9use MediaWiki\Config\ServiceOptions;
10use MediaWiki\Extension\Notifications\Model\Event;
12use MediaWiki\Title\Title;
13use MediaWiki\User\User;
14use MediaWiki\User\UserIdentity;
15use MediaWiki\User\UserIdentityLookup;
16use MessageGroup;
17use Psr\Log\LoggerInterface;
18use StatusValue;
19
27 private MessageGroupSubscriptionStore $groupSubscriptionStore;
28 private JobQueueGroup $jobQueueGroup;
29 private bool $isMessageGroupSubscriptionEnabled;
30 private UserIdentityLookup $userIdentityLookup;
31 private array $queuedMessages = [];
32 private LoggerInterface $logger;
33
34 public const STATE_ADDED = 'added';
35 public const CONSTRUCTOR_OPTIONS = [ 'TranslateEnableMessageGroupSubscription' ];
36
37 public const NOT_ENABLED = 'mgs-not-enabled';
38 public const UNNAMED_USER_UNSUPPORTED = 'mgs-unnamed-user-unsupported';
39 public const DYNAMIC_GROUP_UNSUPPORTED = 'mgs-dynamic-group-unsupported';
40
41 public function __construct(
42 MessageGroupSubscriptionStore $groupSubscriptionStore,
43 JobQueueGroup $jobQueueGroup,
44 UserIdentityLookup $userIdentityLookup,
45 LoggerInterface $logger,
46 ServiceOptions $options
47 ) {
48 $this->groupSubscriptionStore = $groupSubscriptionStore;
49 $this->jobQueueGroup = $jobQueueGroup;
50 $this->userIdentityLookup = $userIdentityLookup;
51 $this->logger = $logger;
52 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
53 $this->isMessageGroupSubscriptionEnabled = $options->get( 'TranslateEnableMessageGroupSubscription' );
54 }
55
56 public function isEnabled(): bool {
57 return $this->isMessageGroupSubscriptionEnabled;
58 }
59
60 public function subscribeToGroup( MessageGroup $group, User $user ): StatusValue {
61 $status = $this->canUserSubscribeToGroup( $group, $user );
62 if ( !$status->isOK() ) {
63 return $status;
64 }
65
66 $this->groupSubscriptionStore->addSubscription( $group->getId(), $user->getId() );
67 return StatusValue::newGood();
68 }
69
70 public function isUserSubscribedTo( MessageGroup $group, UserIdentity $user ): bool {
71 return $this->groupSubscriptionStore->getSubscriptions( [ $group->getId() ], $user->getId() )->count() !== 0;
72 }
73
74 public function unsubscribeFromGroup( MessageGroup $group, UserIdentity $user ): void {
75 $this->groupSubscriptionStore->removeSubscriptions( $group->getId(), $user->getId() );
76 }
77
79 public function getUserSubscriptions( UserIdentity $user ): array {
80 $subscriptions = [];
81 $result = $this->groupSubscriptionStore->getSubscriptions( null, $user->getId() );
82 foreach ( $result as $row ) {
83 $subscriptions[] = $row->tmgs_group;
84 }
85 return $subscriptions;
86 }
87
95 public function queueMessage(
96 Title $messageTitle,
97 string $state,
98 array $groupIds
99 ): void {
100 foreach ( $groupIds as $groupId ) {
101 $this->queuedMessages[ $groupId ][ $state ][] = $messageTitle->getPrefixedDBkey();
102 }
103 }
104
105 public function queueNotificationJob(): void {
106 if ( !$this->isEnabled() || $this->queuedMessages === [] ) {
107 return;
108 }
109
110 $this->jobQueueGroup->push( MessageGroupSubscriptionNotificationJob::newJob( $this->queuedMessages ) );
111 $this->logger->debug(
112 'Queued job with changes for {countGroups} groups',
113 [ 'countGroups' => count( $this->queuedMessages ) ]
114 );
115 // Reset queued messages once job has been queued
116 $this->queuedMessages = [];
117 }
118
119 public function sendNotifications( array $changesToProcess ): void {
120 if ( !$this->isEnabled() || $changesToProcess === [] ) {
121 return;
122 }
123
124 $groupIds = array_keys( $changesToProcess );
125 $allGroupSubscribers = $this->getSubscriberIdsForGroups( $groupIds );
126
127 // No subscribers found for the groups
128 if ( !$allGroupSubscribers ) {
129 $this->logger->info( 'No subscribers for groups.' );
130 return;
131 }
132
133 $groups = MessageGroups::getGroupsById( $groupIds );
134 foreach ( $changesToProcess as $groupId => $state ) {
135 $group = $groups[ $groupId ] ?? null;
136 if ( !$group ) {
137 $this->logger->debug(
138 'Group not found {groupId}.',
139 [ 'groupId' => $groupId ]
140 );
141 continue;
142 }
143
144 $groupSubscribers = $allGroupSubscribers[ $groupId ] ?? [];
145 if ( $groupSubscribers === [] ) {
146 $this->logger->info(
147 'No subscribers found for {groupId} group.',
148 [ 'groupId' => $groupId ]
149 );
150 continue;
151 }
152
153 Event::create( [
154 'type' => 'translate-mgs-message-added',
155 'extra' => [
156 'groupId' => $groupId,
157 'groupLabel' => $group->getLabel(),
158 'changes' => $state
159 ]
160 ] );
161
162 $this->logger->info(
163 'Event created for {groupId} with {subscriberCount} subscribers.',
164 [
165 'groupId' => $groupId,
166 'subscriberCount' => count( $groupSubscribers )
167 ]
168 );
169 }
170 }
171
172 public function handleMessageIndexUpdate( MessageHandle $handle, array $old, array $new ): void {
173 $addedGroups = array_diff( $new, $old );
174 if ( $addedGroups ) {
175 $this->queueMessage( $handle->getTitle(), self::STATE_ADDED, $addedGroups );
176 }
177 }
178
183 public function getGroupSubscribers( string $groupId ): Iterator {
184 $groupSubscriberIds = $this->getSubscriberIdsForGroups( [ $groupId ] );
185 $groupSubscriberIds = $groupSubscriberIds[ $groupId ] ?? [];
186 if ( $groupSubscriberIds === [] ) {
187 return new EmptyIterator();
188 }
189
190 return $this->userIdentityLookup->newSelectQueryBuilder()
191 ->whereUserIds( $groupSubscriberIds )
192 ->caller( __METHOD__ )
193 ->fetchUserIdentities();
194 }
195
202 private function getSubscriberIdsForGroups( array $groupIds ): array {
203 $dbGroupSubscriptions = $this->groupSubscriptionStore->getSubscriptions( $groupIds, null );
204 $groupSubscriptions = [];
205
206 foreach ( $dbGroupSubscriptions as $row ) {
207 $groupSubscriptions[ $row->tmgs_group ][] = (int)$row->tmgs_user_id;
208 }
209
210 return $groupSubscriptions;
211 }
212
213 public function canUserSubscribeToGroup( MessageGroup $group, User $user ): StatusValue {
214 if ( !$this->isEnabled() ) {
215 return StatusValue::newFatal( self::NOT_ENABLED );
216 }
217
218 if ( MessageGroups::isDynamic( $group ) ) {
219 return StatusValue::newFatal( self::DYNAMIC_GROUP_UNSUPPORTED );
220 }
221
222 if ( !$user->isNamed() ) {
223 return StatusValue::newFatal( self::UNNAMED_USER_UNSUPPORTED );
224 }
225
226 return StatusValue::newGood();
227 }
228}
Store service for looking up and storing user subscriptions to message group.
Manage user subscriptions to message groups and trigger notifications.
queueMessage(Title $messageTitle, string $state, array $groupIds)
Queue a message / group to send notifications for.
getGroupSubscribers(string $groupId)
Given a group id returns an iterator to the subscribers of that group.
Class for pointing to messages, like Title class is for titles.
Interface for message groups.
getId()
Returns the unique identifier for this group.
getLabel(IContextSource $context=null)
Returns the human readable label (as plain text).