Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
38.95% |
37 / 95 |
|
25.00% |
3 / 12 |
CRAP | |
0.00% |
0 / 1 |
MessageGroupReviewStore | |
38.95% |
37 / 95 |
|
25.00% |
3 / 12 |
143.38 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getState | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
changeState | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
2 | |||
getGroupPriority | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setGroupPriority | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
preloadGroupPriorities | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getWorkflowState | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getWorkflowStatesForLanguage | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
getWorkflowStatesForGroup | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getWorkflowStates | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
result2map | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getGroupIdForDatabase | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageGroupProcessing; |
5 | |
6 | use InvalidArgumentException; |
7 | use ManualLogEntry; |
8 | use MediaWiki\Extension\Translate\HookRunner; |
9 | use MessageGroup; |
10 | use SpecialPage; |
11 | use User; |
12 | use Wikimedia\Rdbms\ILoadBalancer; |
13 | use Wikimedia\Rdbms\IResultWrapper; |
14 | |
15 | /** |
16 | * Provides methods to get and change the state of a message group |
17 | * @author Eugene Wang'ombe |
18 | * @license GPL-2.0-or-later |
19 | */ |
20 | class MessageGroupReviewStore { |
21 | private HookRunner $hookRunner; |
22 | private ILoadBalancer $loadBalancer; |
23 | private const TABLE_NAME = 'translate_groupreviews'; |
24 | /** Cache for message group priorities: (database group id => value) */ |
25 | private ?array $priorityCache = null; |
26 | |
27 | public function __construct( ILoadBalancer $loadBalancer, HookRunner $hookRunner ) { |
28 | $this->loadBalancer = $loadBalancer; |
29 | $this->hookRunner = $hookRunner; |
30 | } |
31 | |
32 | /** @return mixed|false — The value from the field, or false if nothing was found */ |
33 | public function getState( MessageGroup $group, string $code ) { |
34 | $dbw = $this->loadBalancer->getConnection( DB_PRIMARY ); |
35 | return $dbw->newSelectQueryBuilder() |
36 | ->select( 'tgr_state' ) |
37 | ->from( self::TABLE_NAME ) |
38 | ->where( [ |
39 | 'tgr_group' => self::getGroupIdForDatabase( $group->getId() ), |
40 | 'tgr_lang' => $code |
41 | ] )->fetchField(); |
42 | } |
43 | |
44 | /** @return bool true if the message group state changed, otherwise false */ |
45 | public function changeState( MessageGroup $group, string $code, string $newState, User $user ): bool { |
46 | $currentState = $this->getState( $group, $code ); |
47 | if ( $currentState === $newState ) { |
48 | return false; |
49 | } |
50 | |
51 | $index = [ 'tgr_group', 'tgr_lang' ]; |
52 | $row = [ |
53 | 'tgr_group' => self::getGroupIdForDatabase( $group->getId() ), |
54 | 'tgr_lang' => $code, |
55 | 'tgr_state' => $newState, |
56 | ]; |
57 | $dbw = $this->loadBalancer->getConnection( DB_PRIMARY ); |
58 | $dbw->replace( self::TABLE_NAME, [ $index ], $row, __METHOD__ ); |
59 | |
60 | $entry = new ManualLogEntry( 'translationreview', 'group' ); |
61 | $entry->setPerformer( $user ); |
62 | $entry->setTarget( SpecialPage::getTitleFor( 'Translate', $group->getId() ) ); |
63 | $entry->setParameters( [ |
64 | '4::language' => $code, |
65 | '5::group-label' => $group->getLabel(), |
66 | '6::old-state' => $currentState, |
67 | '7::new-state' => $newState, |
68 | ] ); |
69 | // @todo |
70 | // $entry->setComment( $comment ); |
71 | |
72 | $logId = $entry->insert(); |
73 | $entry->publish( $logId ); |
74 | |
75 | $this->hookRunner->onTranslateEventMessageGroupStateChange( $group, $code, $currentState, $newState ); |
76 | |
77 | return true; |
78 | } |
79 | |
80 | public function getGroupPriority( string $group ): ?string { |
81 | $this->preloadGroupPriorities( __METHOD__ ); |
82 | return $this->priorityCache[self::getGroupIdForDatabase( $group )] ?? null; |
83 | } |
84 | |
85 | /** Store priority for message group. Abusing this table that was intended to store message group states */ |
86 | public function setGroupPriority( string $groupId, ?string $priority ): void { |
87 | $dbGroupId = self::getGroupIdForDatabase( $groupId ); |
88 | if ( isset( $this->priorityCache ) ) { |
89 | $this->priorityCache[$dbGroupId] = $priority; |
90 | } |
91 | |
92 | $dbw = $this->loadBalancer->getConnection( DB_PRIMARY ); |
93 | $row = [ |
94 | 'tgr_group' => $dbGroupId, |
95 | 'tgr_lang' => '*priority', |
96 | 'tgr_state' => $priority |
97 | ]; |
98 | |
99 | if ( $priority === null ) { |
100 | unset( $row['tgr_state'] ); |
101 | $dbw->delete( self::TABLE_NAME, $row, __METHOD__ ); |
102 | } else { |
103 | $index = [ 'tgr_group', 'tgr_lang' ]; |
104 | $dbw->replace( self::TABLE_NAME, [ $index ], $row, __METHOD__ ); |
105 | } |
106 | } |
107 | |
108 | private function preloadGroupPriorities( string $caller ): void { |
109 | if ( isset( $this->priorityCache ) ) { |
110 | return; |
111 | } |
112 | |
113 | $dbr = $this->loadBalancer->getConnection( DB_REPLICA ); |
114 | $res = $dbr->newSelectQueryBuilder() |
115 | ->select( [ 'tgr_group', 'tgr_state' ] ) |
116 | ->from( self::TABLE_NAME ) |
117 | ->where( [ 'tgr_lang' => '*priority' ] ) |
118 | ->caller( $caller ) |
119 | ->fetchResultSet(); |
120 | |
121 | $this->priorityCache = $this->result2map( $res, 'tgr_group', 'tgr_state' ); |
122 | } |
123 | |
124 | /** |
125 | * Get the current workflow state for the given message group for the given language |
126 | * @param string $groupId |
127 | * @param string $languageCode |
128 | * @return string|null State id or null. |
129 | */ |
130 | public function getWorkflowState( string $groupId, string $languageCode ): ?string { |
131 | $result = $this->getWorkflowStates( [ $groupId ], [ $languageCode ] ); |
132 | return $result->fetchRow()['tgr_state'] ?? null; |
133 | } |
134 | |
135 | public function getWorkflowStatesForLanguage( string $languageCode, array $groupIds ): array { |
136 | $result = $this->getWorkflowStates( $groupIds, [ $languageCode ] ); |
137 | $states = $this->result2map( $result, 'tgr_group', 'tgr_state' ); |
138 | |
139 | $finalResult = []; |
140 | foreach ( $groupIds as $groupId ) { |
141 | $dbGroupId = self::getGroupIdForDatabase( $groupId ); |
142 | if ( isset( $states[ $dbGroupId ] ) ) { |
143 | $finalResult[ $groupId ] = $states[ $dbGroupId ]; |
144 | } |
145 | } |
146 | |
147 | return $finalResult; |
148 | } |
149 | |
150 | public function getWorkflowStatesForGroup( string $groupId ): array { |
151 | $result = $this->getWorkflowStates( [ $groupId ], null ); |
152 | return $this->result2map( $result, 'tgr_lang', 'tgr_state' ); |
153 | } |
154 | |
155 | private function getWorkflowStates( ?array $groupIds, ?array $languageCodes ): IResultWrapper { |
156 | $dbr = $this->loadBalancer->getConnection( DB_REPLICA ); |
157 | $conditions = array_filter( |
158 | [ 'tgr_group' => $groupIds, 'tgr_lang' => $languageCodes ], |
159 | static fn ( $x ) => $x !== null && $x !== '' |
160 | ); |
161 | |
162 | if ( $conditions === [] ) { |
163 | throw new InvalidArgumentException( 'Either the $groupId or the $languageCode should be provided' ); |
164 | } |
165 | |
166 | if ( isset( $conditions['tgr_group'] ) ) { |
167 | $conditions['tgr_group'] = array_map( [ self::class, 'getGroupIdForDatabase' ], $groupIds ); |
168 | } |
169 | |
170 | return $dbr->newSelectQueryBuilder() |
171 | ->select( [ 'tgr_state', 'tgr_group', 'tgr_lang' ] ) |
172 | ->from( self::TABLE_NAME ) |
173 | ->where( $conditions ) |
174 | ->caller( __METHOD__ ) |
175 | ->fetchResultSet(); |
176 | } |
177 | |
178 | private function result2map( IResultWrapper $result, string $keyValue, string $valueValue ): array { |
179 | $map = []; |
180 | foreach ( $result as $row ) { |
181 | $map[$row->$keyValue] = $row->$valueValue; |
182 | } |
183 | |
184 | return $map; |
185 | } |
186 | |
187 | private static function getGroupIdForDatabase( $groupId ): string { |
188 | $groupId = strval( $groupId ); |
189 | |
190 | // Check if length is more than 200 bytes |
191 | if ( strlen( $groupId ) <= 200 ) { |
192 | return $groupId; |
193 | } |
194 | |
195 | // We take 160 bytes of the original string and append the md5 hash (32 bytes) |
196 | return mb_strcut( $groupId, 0, 160 ) . '||' . hash( 'md5', $groupId ); |
197 | } |
198 | } |