Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroupMetadata.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageProcessing;
5
10use Wikimedia\Rdbms\IConnectionProvider;
11
23 private const MAX_ITEMS_PER_QUERY = 2000;
25 private array $cache = [];
26 private ?array $priorityCache = null;
27
28 public function __construct( private readonly IConnectionProvider $dbProvider ) {
29 }
30
35 public function preloadGroups( array $groups, string $caller ): void {
36 $dbGroupIds = array_map( MessageGroupSubscriptionStore::getGroupIdForDatabase( ... ), $groups );
37 $missing = array_keys( array_diff_key( array_flip( $dbGroupIds ), $this->cache ) );
38 if ( !$missing ) {
39 return;
40 }
41
42 $functionName = __METHOD__ . " (for $caller)";
43
44 $this->cache += array_fill_keys( $missing, null ); // cache negatives
45
46 // TODO: Ideally, this should use the injected ILoadBalancer to make it mockable.
47 $dbr = Utilities::getSafeReadDB();
48 $chunks = array_chunk( $missing, self::MAX_ITEMS_PER_QUERY );
49 foreach ( $chunks as $chunk ) {
50 $res = $dbr->newSelectQueryBuilder()
51 ->select( [ 'tmd_group', 'tmd_key', 'tmd_value' ] )
52 ->from( 'translate_metadata' )
53 ->where( [ 'tmd_group' => array_map( strval( ... ), $chunk ) ] )
54 ->caller( $functionName )
55 ->fetchResultSet();
56 foreach ( $res as $row ) {
57 $this->cache[$row->tmd_group][$row->tmd_key] = $row->tmd_value;
58 }
59 }
60 }
61
67 public function get( MessageGroup|string $group, string $key ): string|false {
68 $this->preloadGroups( [ $group ], __METHOD__ );
69 $dbGroupId = MessageGroupSubscriptionStore::getGroupIdForDatabase( $group );
70 return $this->cache[$dbGroupId][$key] ?? false;
71 }
72
77 public function getWithDefaultValue( string $group, string $key, ?string $defaultValue ): ?string {
78 $value = $this->get( $group, $key );
79 return $value === false ? $defaultValue : $value;
80 }
81
89 public function set( MessageGroup|string $group, string $key, string|false $value ): void {
90 $dbw = $this->dbProvider->getPrimaryDatabase();
91 $dbGroupId = MessageGroupSubscriptionStore::getGroupIdForDatabase( $group );
92 $data = [ 'tmd_group' => $dbGroupId, 'tmd_key' => $key, 'tmd_value' => $value ];
93 if ( $value === false ) {
94 unset( $data['tmd_value'] );
95 $dbw->newDeleteQueryBuilder()
96 ->deleteFrom( 'translate_metadata' )
97 ->where( $data )
98 ->caller( __METHOD__ )
99 ->execute();
100 unset( $this->cache[$dbGroupId][$key] );
101 } else {
102 $dbw->newReplaceQueryBuilder()
103 ->replaceInto( 'translate_metadata' )
104 ->uniqueIndexFields( [ 'tmd_group', 'tmd_key' ] )
105 ->row( $data )
106 ->caller( __METHOD__ )
107 ->execute();
108 $this->cache[$dbGroupId][$key] = $value;
109 }
110
111 $this->priorityCache = null;
112 }
113
118 public function getSubgroups( string $groupId ): ?array {
119 $groups = $this->get( $groupId, 'subgroups' );
120 if ( is_string( $groups ) ) {
121 if ( str_contains( $groups, '|' ) ) {
122 $groups = explode( '|', $groups );
123 } else {
124 $groups = array_map( 'trim', explode( ',', $groups ) );
125 }
126
127 foreach ( $groups as $index => $id ) {
128 if ( trim( $id ) === '' ) {
129 unset( $groups[$index] );
130 }
131 }
132 } else {
133 $groups = null;
134 }
135
136 return $groups;
137 }
138
140 public function setSubgroups( string $groupId, array $subgroupIds ): void {
141 $subgroups = implode( '|', $subgroupIds );
142 $this->set( $groupId, 'subgroups', $subgroups );
143 }
144
146 public function deleteGroup( string $groupId ): void {
147 $dbw = $this->dbProvider->getPrimaryDatabase();
148
149 $dbGroupId = MessageGroupSubscriptionStore::getGroupIdForDatabase( $groupId );
150 $dbw->newDeleteQueryBuilder()
151 ->deleteFrom( 'translate_metadata' )
152 ->where( [ 'tmd_group' => $dbGroupId ] )
153 ->caller( __METHOD__ )
154 ->execute();
155 $this->cache[ $dbGroupId ] = null;
156 unset( $this->priorityCache[ $dbGroupId ] );
157 }
158
159 public function isExcluded( MessageGroup|string $group, string $code ): bool {
160 if ( $this->priorityCache === null ) {
161 // TODO: Ideally, this should use the injected ILoadBalancer to make it mockable.
162 $db = Utilities::getSafeReadDB();
163 $res = $db->newSelectQueryBuilder()
164 ->select( [
165 'group' => 'a.tmd_group',
166 'langs' => 'b.tmd_value',
167 ] )
168 ->from( 'translate_metadata', 'a' )
169 ->leftJoin( 'translate_metadata', 'b', [
170 'a.tmd_group = b.tmd_group',
171 'b.tmd_key' => 'prioritylangs',
172 ] )
173 ->where( [
174 'a.tmd_key' => 'priorityforce',
175 'a.tmd_value' => 'on'
176 ] )
177 ->caller( __METHOD__ )
178 ->fetchResultSet();
179
180 $this->priorityCache = [];
181 foreach ( $res as $row ) {
182 $this->priorityCache[$row->group] = isset( $row->langs )
183 ? array_flip( explode( ',', $row->langs ) )
184 : [];
185 }
186 }
187
188 $dbGroupId = MessageGroupSubscriptionStore::getGroupIdForDatabase( $group );
189 $isDiscouraged = MessageGroups::getPriority( $group ) === 'discouraged';
190 $hasLimitedLanguages = isset( $this->priorityCache[$dbGroupId] );
191 $isLanguageIncluded = isset( $this->priorityCache[$dbGroupId][$code] );
192
193 return $isDiscouraged || ( $hasLimitedLanguages && !$isLanguageIncluded );
194 }
195
202 public function loadBasicMetadataForTranslatablePages( array $groupIds, array $keys ): array {
203 // TODO: Ideally, this should use the injected ILoadBalancer to make it mockable.
204 $db = Utilities::getSafeReadDB();
205 $dbGroupIdMap = [];
206
207 foreach ( $groupIds as $groupId ) {
208 $dbGroupId = MessageGroupSubscriptionStore::getGroupIdForDatabase( $groupId );
209 $dbGroupIdMap[$dbGroupId] = $groupId;
210 }
211
212 $res = $db->newSelectQueryBuilder()
213 ->select( [ 'tmd_group', 'tmd_key', 'tmd_value' ] )
214 ->from( 'translate_metadata' )
215 ->where( [
216 'tmd_group' => array_keys( $dbGroupIdMap ),
217 'tmd_key' => $keys,
218 ] )
219 ->caller( __METHOD__ )
220 ->fetchResultSet();
221
222 $ret = [];
223 foreach ( $res as $row ) {
224 $groupId = $row->tmd_group;
225 // Remap the db group ids to group id in the response
226 $ret[ $dbGroupIdMap[ $groupId ] ][ $row->tmd_key ] = $row->tmd_value;
227 }
228
229 return $ret;
230 }
231
232 public function moveMetadata(
233 string $oldGroupId,
234 string $newGroupId,
235 array $metadataKeysToMove
236 ): void {
237 $this->preloadGroups( [ $oldGroupId, $newGroupId ], __METHOD__ );
238 foreach ( $metadataKeysToMove as $type ) {
239 $value = $this->get( $oldGroupId, $type );
240 if ( $value !== false ) {
241 $this->set( $oldGroupId, $type, false );
242 $this->set( $newGroupId, $type, $value );
243 }
244 }
245 }
246
251 public function clearMetadata( string $groupId, array $metadataKeys ): void {
252 // remove the entries from metadata table.
253 foreach ( $metadataKeys as $type ) {
254 $this->set( $groupId, $type, false );
255 }
256 }
257
259 public function getGroupsWithSubgroups(): array {
260 // TODO: Ideally, this should use the injected ILoadBalancer to make it mockable.
261 $db = Utilities::getSafeReadDB();
262 // There is no need to de-hash the group id from the database as
263 // AggregateGroupsActionApi::generateAggregateGroupId already ensures that the length
264 // is appropriate
265 return $db->newSelectQueryBuilder()
266 ->select( 'tmd_group' )
267 ->from( 'translate_metadata' )
268 ->where( [ 'tmd_key' => 'subgroups' ] )
269 ->caller( __METHOD__ )
270 ->fetchFieldValues();
271 }
272
273}
Store service for looking up and storing user subscriptions to message group.
Factory class for accessing message groups individually by id or all of them as a list.
Offers functionality for reading and updating Translate group related metadata.
getWithDefaultValue(string $group, string $key, ?string $defaultValue)
Get a metadata value for the given group and key.
loadBasicMetadataForTranslatablePages(array $groupIds, array $keys)
Do a query optimized for page list in Special:PageTranslation.
setSubgroups(string $groupId, array $subgroupIds)
Wrapper for setting subgroups.
deleteGroup(string $groupId)
Wrapper for deleting one wiki aggregate group at once.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
Interface for message groups.