Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 100 |
|
0.00% |
0 / 18 |
CRAP | |
0.00% |
0 / 1 |
MessageHandle | |
0.00% |
0 / 100 |
|
0.00% |
0 / 18 |
1260 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isMessageNamespace | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
figureMessage | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
getKey | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getCode | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getEffectiveLanguage | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
isDoc | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isPageTranslation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupIds | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getGroup | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
isValid | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
30 | |||
getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTitleForLanguage | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getTitleForBase | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
hasFuzzyString | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeFuzzyString | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
isFuzzy | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
getInternalKey | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageLoading; |
5 | |
6 | use BadMethodCallException; |
7 | use Language; |
8 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
9 | use MediaWiki\Extension\Translate\MessageGroupProcessing\RevTagStore; |
10 | use MediaWiki\Extension\Translate\Services; |
11 | use MediaWiki\Linker\LinkTarget; |
12 | use MediaWiki\Logger\LoggerFactory; |
13 | use MediaWiki\MediaWikiServices; |
14 | use MediaWiki\Title\Title; |
15 | use MessageGroup; |
16 | use MessageIndexRebuildJob; |
17 | |
18 | /** |
19 | * Class for pointing to messages, like Title class is for titles. |
20 | * Also enhances Title with stuff related to message groups |
21 | * @author Niklas Laxström |
22 | * @copyright Copyright © 2011-2013 Niklas Laxström |
23 | * @license GPL-2.0-or-later |
24 | */ |
25 | class MessageHandle { |
26 | private LinkTarget $title; |
27 | private ?string $key = null; |
28 | private ?string $languageCode = null; |
29 | /** @var string[]|null */ |
30 | private ?array $groupIds = null; |
31 | private MessageIndex $messageIndex; |
32 | |
33 | public function __construct( LinkTarget $title ) { |
34 | $this->title = $title; |
35 | $this->messageIndex = Services::getInstance()->getMessageIndex(); |
36 | } |
37 | |
38 | /** Check if this handle is in a message namespace. */ |
39 | public function isMessageNamespace(): bool { |
40 | global $wgTranslateMessageNamespaces; |
41 | $namespace = $this->title->getNamespace(); |
42 | |
43 | return in_array( $namespace, $wgTranslateMessageNamespaces ); |
44 | } |
45 | |
46 | /** |
47 | * Recommended to use getCode and getKey instead. |
48 | * @return string[] Array of the message key and the language code |
49 | */ |
50 | public function figureMessage(): array { |
51 | if ( $this->key === null ) { |
52 | // Check if this is a valid message first |
53 | $this->key = $this->title->getDBkey(); |
54 | $known = $this->messageIndex->getGroupIds( $this ) !== []; |
55 | |
56 | $pos = strrpos( $this->key, '/' ); |
57 | if ( $known || $pos === false ) { |
58 | $this->languageCode = ''; |
59 | } else { |
60 | // For keys like Foo/, substr returns false instead of '' |
61 | $this->languageCode = (string)( substr( $this->key, $pos + 1 ) ); |
62 | $this->key = substr( $this->key, 0, $pos ); |
63 | } |
64 | } |
65 | |
66 | return [ $this->key, $this->languageCode ]; |
67 | } |
68 | |
69 | /** Returns the identified or guessed message key. */ |
70 | public function getKey(): string { |
71 | $this->figureMessage(); |
72 | |
73 | return $this->key; |
74 | } |
75 | |
76 | /** |
77 | * Returns the language code. |
78 | * For language codeless source messages will return empty string. |
79 | */ |
80 | public function getCode(): string { |
81 | $this->figureMessage(); |
82 | |
83 | return $this->languageCode; |
84 | } |
85 | |
86 | /** |
87 | * Return the Language object for the assumed language of the content, which might |
88 | * be different from the subpage code (qqq, no subpage). |
89 | */ |
90 | public function getEffectiveLanguage(): Language { |
91 | $code = $this->getCode(); |
92 | $mwServices = MediaWikiServices::getInstance(); |
93 | if ( !$mwServices->getLanguageNameUtils()->isKnownLanguageTag( $code ) || |
94 | $this->isDoc() |
95 | ) { |
96 | return $mwServices->getContentLanguage(); |
97 | } |
98 | |
99 | return $mwServices->getLanguageFactory()->getLanguage( $code ); |
100 | } |
101 | |
102 | /** Determine whether the current handle is for message documentation. */ |
103 | public function isDoc(): bool { |
104 | global $wgTranslateDocumentationLanguageCode; |
105 | |
106 | return $this->getCode() === $wgTranslateDocumentationLanguageCode; |
107 | } |
108 | |
109 | /** |
110 | * Determine whether the current handle is for page translation feature. |
111 | * This does not consider whether the handle corresponds to any message. |
112 | */ |
113 | public function isPageTranslation(): bool { |
114 | return $this->title->inNamespace( NS_TRANSLATIONS ); |
115 | } |
116 | |
117 | /** |
118 | * Returns all message group ids this message belongs to. |
119 | * The primary message group id is always the first one. |
120 | * If the handle does not correspond to any message, the returned array |
121 | * is empty. |
122 | * @return string[] |
123 | */ |
124 | public function getGroupIds() { |
125 | if ( $this->groupIds === null ) { |
126 | $this->groupIds = $this->messageIndex->getGroupIds( $this ); |
127 | } |
128 | |
129 | return $this->groupIds; |
130 | } |
131 | |
132 | /** |
133 | * Get the primary MessageGroup this message belongs to. |
134 | * You should check first that the handle is valid. |
135 | */ |
136 | public function getGroup(): ?MessageGroup { |
137 | $ids = $this->getGroupIds(); |
138 | if ( !isset( $ids[0] ) ) { |
139 | throw new BadMethodCallException( 'called before isValid' ); |
140 | } |
141 | return MessageGroups::getGroup( $ids[0] ); |
142 | } |
143 | |
144 | /** Checks if the handle corresponds to a known message. */ |
145 | public function isValid(): bool { |
146 | static $jobHasBeenScheduled = false; |
147 | |
148 | if ( !$this->isMessageNamespace() ) { |
149 | return false; |
150 | } |
151 | |
152 | $groups = $this->getGroupIds(); |
153 | if ( !$groups ) { |
154 | return false; |
155 | } |
156 | |
157 | // Do another check that the group actually exists |
158 | $group = $this->getGroup(); |
159 | if ( !$group ) { |
160 | $logger = LoggerFactory::getInstance( 'Translate' ); |
161 | $logger->warning( |
162 | '[MessageHandle] MessageIndex is out of date. Page {pagename} refers to ' . |
163 | 'unknown group {messagegroup}', |
164 | [ |
165 | 'pagename' => $this->getTitle()->getPrefixedText(), |
166 | 'messagegroup' => $groups[0], |
167 | ] |
168 | ); |
169 | |
170 | if ( !$jobHasBeenScheduled ) { |
171 | // Schedule a job in the job queue (with deduplication) |
172 | $job = MessageIndexRebuildJob::newJob( __METHOD__ ); |
173 | MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $job ); |
174 | $jobHasBeenScheduled = true; |
175 | } |
176 | |
177 | return false; |
178 | } |
179 | |
180 | return true; |
181 | } |
182 | |
183 | /** Get the original title. */ |
184 | public function getTitle(): Title { |
185 | return Title::newFromLinkTarget( $this->title ); |
186 | } |
187 | |
188 | /** Get the original title with the passed language code. */ |
189 | public function getTitleForLanguage( string $languageCode ): Title { |
190 | return Title::makeTitle( |
191 | $this->title->getNamespace(), |
192 | $this->getKey() . "/$languageCode" |
193 | ); |
194 | } |
195 | |
196 | /** Get the title for the page base. */ |
197 | public function getTitleForBase(): Title { |
198 | return Title::makeTitle( |
199 | $this->title->getNamespace(), |
200 | $this->getKey() |
201 | ); |
202 | } |
203 | |
204 | /** |
205 | * Check if a string contains the fuzzy string. |
206 | * @param string $text Arbitrary text |
207 | * @return bool If string contains fuzzy string. |
208 | */ |
209 | public static function hasFuzzyString( string $text ): bool { |
210 | return str_contains( $text, TRANSLATE_FUZZY ); |
211 | } |
212 | |
213 | /** Check if a string has fuzzy string and if not, add it */ |
214 | public static function makeFuzzyString( string $text ): string { |
215 | return self::hasFuzzyString( $text ) ? $text : TRANSLATE_FUZZY . $text; |
216 | } |
217 | |
218 | /** Check if a title is marked as fuzzy. */ |
219 | public function isFuzzy(): bool { |
220 | $dbr = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_REPLICA ); |
221 | |
222 | $res = $dbr->newSelectQueryBuilder() |
223 | ->select( 'rt_type' ) |
224 | ->from( 'page' ) |
225 | ->join( 'revtag', null, [ |
226 | 'page_id=rt_page', |
227 | 'page_latest=rt_revision', |
228 | 'rt_type' => RevTagStore::FUZZY_TAG, |
229 | ] ) |
230 | ->where( [ |
231 | 'page_namespace' => $this->title->getNamespace(), |
232 | 'page_title' => $this->title->getDBkey(), |
233 | ] ) |
234 | ->caller( __METHOD__ ) |
235 | ->fetchField(); |
236 | |
237 | return $res !== false; |
238 | } |
239 | |
240 | /** |
241 | * This returns the key that can be used for showMessage parameter for Special:Translate |
242 | * for regular message groups. It is not possible to automatically determine this key |
243 | * from the title alone. |
244 | */ |
245 | public function getInternalKey(): string { |
246 | $mwServices = MediaWikiServices::getInstance(); |
247 | $nsInfo = $mwServices->getNamespaceInfo(); |
248 | $contentLanguage = $mwServices->getContentLanguage(); |
249 | |
250 | $key = $this->getKey(); |
251 | $group = $this->getGroup(); |
252 | $groupKeys = $group->getKeys(); |
253 | |
254 | if ( in_array( $key, $groupKeys, true ) ) { |
255 | return $key; |
256 | } |
257 | |
258 | $namespace = $this->title->getNamespace(); |
259 | if ( $nsInfo->isCapitalized( $namespace ) ) { |
260 | $lowercaseKey = $contentLanguage->lcfirst( $key ); |
261 | if ( in_array( $lowercaseKey, $groupKeys, true ) ) { |
262 | return $lowercaseKey; |
263 | } |
264 | } |
265 | |
266 | // Brute force all the keys to find the one. This one should always find a match |
267 | // if there is one. |
268 | foreach ( $groupKeys as $haystackKey ) { |
269 | $normalizedHaystackKey = Title::makeTitleSafe( $namespace, $haystackKey )->getDBkey(); |
270 | if ( $normalizedHaystackKey === $key ) { |
271 | return $haystackKey; |
272 | } |
273 | } |
274 | |
275 | return "BUG:$key"; |
276 | } |
277 | } |