Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 68 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
RevTagStore | |
0.00% |
0 / 68 |
|
0.00% |
0 / 6 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
replaceTag | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
getLatestRevisionWithTag | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getLatestRevisionsForTags | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
42 | |||
removeTags | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getTranslatableBundleIds | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageGroupProcessing; |
5 | |
6 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
7 | use MediaWiki\Page\PageIdentity; |
8 | use Wikimedia\Rdbms\ILoadBalancer; |
9 | |
10 | /** |
11 | * Class to manage revision tags for translatable bundles. |
12 | * @author Abijeet Patro |
13 | * @author Niklas Laxström |
14 | * @since 2022.04 |
15 | * @license GPL-2.0-or-later |
16 | */ |
17 | class RevTagStore { |
18 | /** Indicates that a translation is fuzzy (outdated or not passing validation). */ |
19 | public const FUZZY_TAG = 'fuzzy'; |
20 | /** Stores the revision id of the source text which was translated. Used for showing |
21 | * diffs for outdated messages. |
22 | */ |
23 | public const TRANSVER_PROP = 'tp:transver'; |
24 | /** Indicates a revision of a page that can be marked for translation. */ |
25 | public const TP_MARK_TAG = 'tp:mark'; |
26 | /** Indicates a revision of a translatable page that is marked for translation. */ |
27 | public const TP_READY_TAG = 'tp:tag'; |
28 | /** Indicates a revision of a page that is a valid message bundle. */ |
29 | public const MB_VALID_TAG = 'mb:valid'; |
30 | |
31 | private ILoadBalancer $loadBalancer; |
32 | private array $tagCache = []; |
33 | |
34 | public function __construct( ILoadBalancer $loadBalancer ) { |
35 | $this->loadBalancer = $loadBalancer; |
36 | } |
37 | |
38 | /** Add tag for the given revisionId, while deleting it from others */ |
39 | public function replaceTag( |
40 | PageIdentity $identity, |
41 | string $tag, |
42 | int $revisionId, |
43 | ?array $value = null |
44 | ): void { |
45 | if ( !$identity->exists() ) { |
46 | return; |
47 | } |
48 | |
49 | $articleId = $identity->getId(); |
50 | |
51 | $dbw = $this->loadBalancer->getConnection( DB_PRIMARY ); |
52 | $conds = [ |
53 | 'rt_page' => $articleId, |
54 | 'rt_type' => $tag |
55 | ]; |
56 | $dbw->delete( 'revtag', $conds, __METHOD__ ); |
57 | |
58 | if ( $value !== null ) { |
59 | $conds['rt_value'] = serialize( implode( '|', $value ) ); |
60 | } |
61 | |
62 | $conds['rt_revision'] = $revisionId; |
63 | $dbw->insert( 'revtag', $conds, __METHOD__ ); |
64 | |
65 | $this->tagCache[$articleId][$tag] = $revisionId; |
66 | } |
67 | |
68 | public function getLatestRevisionWithTag( PageIdentity $identity, string $tag ): ?int { |
69 | $response = $this->getLatestRevisionsForTags( $identity, $tag ); |
70 | return $response[$tag] ?? null; |
71 | } |
72 | |
73 | /** @return null|int[] */ |
74 | public function getLatestRevisionsForTags( PageIdentity $identity, string ...$tags ): ?array { |
75 | if ( !$identity->exists() ) { |
76 | return null; |
77 | } |
78 | |
79 | $articleId = $identity->getId(); |
80 | |
81 | $response = []; |
82 | $remainingTags = []; |
83 | |
84 | // ATTENTION: Cache should only be updated on POST requests. |
85 | foreach ( $tags as $tag ) { |
86 | if ( isset( $this->tagCache[$articleId][$tag] ) ) { |
87 | $response[$tag] = $this->tagCache[$articleId][$tag]; |
88 | } else { |
89 | $remainingTags[] = $tag; |
90 | } |
91 | } |
92 | |
93 | if ( !$remainingTags ) { |
94 | // All tags were available in the cache, no need to run any queries. |
95 | return $response; |
96 | } |
97 | |
98 | $dbr = Utilities::getSafeReadDB(); |
99 | $results = $dbr->newSelectQueryBuilder() |
100 | ->select( [ 'rt_revision' => 'MAX(rt_revision)', 'rt_type' ] ) |
101 | ->from( 'revtag' ) |
102 | ->where( [ |
103 | 'rt_page' => $articleId, |
104 | 'rt_type' => $remainingTags |
105 | ] ) |
106 | ->groupBy( 'rt_type' ) |
107 | ->caller( __METHOD__ ) |
108 | ->fetchResultSet(); |
109 | |
110 | foreach ( $results as $row ) { |
111 | $response[$row->rt_type] = (int)$row->rt_revision; |
112 | } |
113 | |
114 | return $response; |
115 | } |
116 | |
117 | public function removeTags( PageIdentity $identity, string ...$tag ): void { |
118 | if ( !$identity->exists() ) { |
119 | return; |
120 | } |
121 | |
122 | $articleId = $identity->getId(); |
123 | |
124 | $dbw = $this->loadBalancer->getConnection( DB_PRIMARY ); |
125 | $conds = [ |
126 | 'rt_page' => $articleId, |
127 | 'rt_type' => $tag, |
128 | ]; |
129 | $dbw->delete( 'revtag', $conds, __METHOD__ ); |
130 | |
131 | unset( $this->tagCache[$articleId] ); |
132 | } |
133 | |
134 | /** Get a list of page ids where the latest revision is either tagged or marked */ |
135 | public static function getTranslatableBundleIds( string ...$revTags ): array { |
136 | $dbr = Utilities::getSafeReadDB(); |
137 | $res = $dbr->newSelectQueryBuilder() |
138 | ->select( 'rt_page' ) |
139 | ->from( 'revtag' ) |
140 | ->join( |
141 | 'page', |
142 | null, |
143 | [ 'rt_page = page_id', 'rt_revision = page_latest', 'rt_type' => $revTags ] |
144 | ) |
145 | ->groupBy( 'rt_page' ) |
146 | ->caller( __METHOD__ ) |
147 | ->fetchResultSet(); |
148 | $results = []; |
149 | foreach ( $res as $row ) { |
150 | $results[$row->rt_page] = true; |
151 | } |
152 | |
153 | return $results; |
154 | } |
155 | } |