Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslateMetadata.php
Go to the documentation of this file.
1<?php
2
5use MediaWiki\MediaWikiServices;
6
20 private static array $cache = [];
21 private static ?array $priorityCache = null;
22
27 public static function preloadGroups( array $groups, string $caller ): void {
28 $dbGroupIds = array_map( [ self::class, 'getGroupIdForDatabase' ], $groups );
29 $missing = array_keys( array_diff_key( array_flip( $dbGroupIds ), self::$cache ) );
30 if ( !$missing ) {
31 return;
32 }
33
34 $functionName = __METHOD__ . " (for $caller)";
35
36 self::$cache += array_fill_keys( $missing, null ); // cache negatives
37
38 $dbr = Utilities::getSafeReadDB();
39 $conditions = count( $missing ) <= 500 ? [ 'tmd_group' => array_map( 'strval', $missing ) ] : [];
40 $res = $dbr->select(
41 'translate_metadata',
42 [ 'tmd_group', 'tmd_key', 'tmd_value' ],
43 $conditions,
44 $functionName
45 );
46 foreach ( $res as $row ) {
47 self::$cache[$row->tmd_group][$row->tmd_key] = $row->tmd_value;
48 }
49 }
50
57 public static function get( string $group, string $key ) {
58 self::preloadGroups( [ $group ], __METHOD__ );
59 return self::$cache[self::getGroupIdForDatabase( $group )][$key] ?? false;
60 }
61
66 public static function getWithDefaultValue(
67 string $group, string $key, string $defaultValue
68 ): string {
69 $value = self::get( $group, $key );
70 return $value === false ? $defaultValue : $value;
71 }
72
80 public static function set( string $groupId, string $key, $value ): void {
81 $dbw = MediaWikiServices::getInstance()
82 ->getDBLoadBalancer()
83 ->getConnection( DB_PRIMARY );
84 $dbGroupId = self::getGroupIdForDatabase( $groupId );
85 $data = [ 'tmd_group' => $dbGroupId, 'tmd_key' => $key, 'tmd_value' => $value ];
86 if ( $value === false ) {
87 unset( $data['tmd_value'] );
88 $dbw->delete( 'translate_metadata', $data, __METHOD__ );
89 unset( self::$cache[$dbGroupId][$key] );
90 } else {
91 $dbw->replace(
92 'translate_metadata',
93 [ [ 'tmd_group', 'tmd_key' ] ],
94 $data,
95 __METHOD__
96 );
97 self::$cache[$dbGroupId][$key] = $value;
98 }
99
100 self::$priorityCache = null;
101 }
102
107 public static function getSubgroups( string $groupId ): ?array {
108 $groups = self::get( $groupId, 'subgroups' );
109 if ( is_string( $groups ) ) {
110 if ( str_contains( $groups, '|' ) ) {
111 $groups = explode( '|', $groups );
112 } else {
113 $groups = array_map( 'trim', explode( ',', $groups ) );
114 }
115
116 foreach ( $groups as $index => $id ) {
117 if ( trim( $id ) === '' ) {
118 unset( $groups[$index] );
119 }
120 }
121 } else {
122 $groups = null;
123 }
124
125 return $groups;
126 }
127
129 public static function setSubgroups( string $groupId, array $subgroupIds ): void {
130 $subgroups = implode( '|', $subgroupIds );
131 self::set( $groupId, 'subgroups', $subgroups );
132 }
133
135 public static function deleteGroup( string $groupId ): void {
136 $dbw = MediaWikiServices::getInstance()
137 ->getDBLoadBalancer()
138 ->getConnection( DB_PRIMARY );
139
140 $dbGroupId = self::getGroupIdForDatabase( $groupId );
141 $conditions = [ 'tmd_group' => $dbGroupId ];
142 $dbw->delete( 'translate_metadata', $conditions, __METHOD__ );
143 self::$cache[ $dbGroupId ] = null;
144 unset( self::$priorityCache[ $dbGroupId ] );
145 }
146
147 public static function isExcluded( string $groupId, string $code ): bool {
148 if ( self::$priorityCache === null ) {
149 $db = Utilities::getSafeReadDB();
150 $res = $db->select(
151 [
152 'a' => 'translate_metadata',
153 'b' => 'translate_metadata'
154 ],
155 [
156 'group' => 'b.tmd_group',
157 'langs' => 'b.tmd_value',
158 ],
159 [],
160 __METHOD__,
161 [],
162 [
163 'b' => [
164 'INNER JOIN',
165 [
166 'a.tmd_group = b.tmd_group',
167 'a.tmd_key' => 'priorityforce',
168 'a.tmd_value' => 'on',
169 'b.tmd_key' => 'prioritylangs',
170 ]
171 ]
172 ]
173 );
174
175 self::$priorityCache = [];
176 foreach ( $res as $row ) {
177 self::$priorityCache[$row->group] =
178 array_flip( explode( ',', $row->langs ) );
179 }
180 }
181
182 $dbGroupId = self::getGroupIdForDatabase( $groupId );
183 $isDiscouraged = MessageGroups::getPriority( $groupId ) === 'discouraged';
184 $hasLimitedLanguages = isset( self::$priorityCache[$dbGroupId] );
185 $isLanguageIncluded = isset( self::$priorityCache[$dbGroupId][$code] );
186
187 return $isDiscouraged || ( $hasLimitedLanguages && !$isLanguageIncluded );
188 }
189
196 public static function loadBasicMetadataForTranslatablePages( array $groupIds, array $keys ): array {
197 $db = Utilities::getSafeReadDB();
198 $dbGroupIdMap = [];
199
200 foreach ( $groupIds as $groupId ) {
201 $dbGroupIdMap[ self::getGroupIdForDatabase( $groupId ) ] = $groupId;
202 }
203
204 $res = $db->select(
205 'translate_metadata',
206 [ 'tmd_group', 'tmd_key', 'tmd_value' ],
207 [
208 'tmd_group' => array_keys( $dbGroupIdMap ),
209 'tmd_key' => $keys,
210 ],
211 __METHOD__
212 );
213
214 $ret = [];
215 foreach ( $res as $row ) {
216 $groupId = $row->tmd_group;
217 // Remap the db group ids to group id in the response
218 $ret[ $dbGroupIdMap[ $groupId ] ][ $row->tmd_key ] = $row->tmd_value;
219 }
220
221 return $ret;
222 }
223
224 public static function moveMetadata(
225 string $oldGroupId,
226 string $newGroupId,
227 array $metadataKeysToMove
228 ): void {
229 self::preloadGroups( [ $oldGroupId, $newGroupId ], __METHOD__ );
230 foreach ( $metadataKeysToMove as $type ) {
231 $value = self::get( $oldGroupId, $type );
232 if ( $value !== false ) {
233 self::set( $oldGroupId, $type, false );
234 self::set( $newGroupId, $type, $value );
235 }
236 }
237 }
238
243 public static function clearMetadata( string $groupId, array $metadataKeys ): void {
244 // remove the entries from metadata table.
245 foreach ( $metadataKeys as $type ) {
246 self::set( $groupId, $type, false );
247 }
248 }
249
251 public static function getGroupsWithSubgroups(): array {
252 $tables = [ 'translate_metadata' ];
253 $field = 'tmd_group';
254 $conditions = [ 'tmd_key' => 'subgroups' ];
255
256 $db = Utilities::getSafeReadDB();
257 // There is no need to de-hash the group id from the database as
258 // AggregateGroupsActionApi::generateAggregateGroupId already ensures that the length
259 // is appropriate
260 return $db->selectFieldValues( $tables, $field, $conditions, __METHOD__ );
261 }
262
263 private static function getGroupIdForDatabase( string $groupId ): string {
264 // Check if length is more than 200 bytes
265 if ( strlen( $groupId ) <= 200 ) {
266 return $groupId;
267 }
268
269 $hash = hash( 'md5', $groupId );
270 // We take 160 bytes of the original string and append the md5 hash (32 bytes)
271 return mb_strcut( $groupId, 0, 160 ) . '||' . $hash;
272 }
273}
Factory class for accessing message groups individually by id or all of them as a list.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
static getWithDefaultValue(string $group, string $key, string $defaultValue)
Get a metadata value for the given group and key.
static getSubgroups(string $groupId)
Wrapper for getting subgroups.
static setSubgroups(string $groupId, array $subgroupIds)
Wrapper for setting subgroups.
static preloadGroups(array $groups, string $caller)
static loadBasicMetadataForTranslatablePages(array $groupIds, array $keys)
Do a query optimized for page list in Special:PageTranslation.
static clearMetadata(string $groupId, array $metadataKeys)
static getGroupsWithSubgroups()
Get groups ids that have subgroups set up.
static deleteGroup(string $groupId)
Wrapper for deleting one wiki aggregate group at once.