ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
MigratedLanguageFileSyncEntryRulesTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
28use PHPUnit\Framework\Attributes\DataProvider;
29use PHPUnit\Framework\TestCase;
30
35{
37 #[DataProvider('belongsInDeltaCases')]
38 public function testBelongsInDelta(?string $shipped_value, string $value, bool $expected): void
39 {
40 $this->assertSame($expected, MigratedLanguageFileSync::belongsInDelta($shipped_value, $value));
41 }
42
46 public static function belongsInDeltaCases(): array
47 {
48 return [
49 'differs from shipped' => ['Hallo', 'Servus', true],
50 'same as shipped' => ['Hallo', 'Hallo', false],
51 'key not shipped at all - any non-empty value belongs' => [null, 'Eigener Wert', true],
52 'empty value resets to shipped, even without a shipped key' => [null, '', false],
53 'empty value resets to shipped, even when shipped differs' => ['Hallo', '', false],
54 'shipped value itself is empty, value matches' => ['', '', false],
55 'shipped value itself is empty, value differs' => ['', 'x', true],
56 'the literal string "0" belongs when shipped differs' => ['1', '0', true],
57 'the literal string "0" is not "empty" and does not reset' => [null, '0', true],
58 ];
59 }
60
65 #[DataProvider('resolveLocalValueCases')]
66 public function testResolveLocalValue(?string $shipped_value, string $value, string $expected): void
67 {
68 $this->assertSame($expected, MigratedLanguageFileSync::resolveLocalValue($shipped_value, $value));
69 }
70
74 public static function resolveLocalValueCases(): array
75 {
76 return [
77 'empty value of a shipped key resolves to the shipped value' => ['Hallo', '', 'Hallo'],
78 'empty value of a key that is not shipped stays empty' => [null, '', ''],
79 'a non-empty value is returned unchanged, shipped key' => ['Hallo', 'Servus', 'Servus'],
80 'a non-empty value is returned unchanged, not a shipped key' => [null, 'Eigener Wert', 'Eigener Wert'],
81 'the shipped value itself is empty - stays empty (nothing to reset to in practice)' => ['', '', ''],
82 ];
83 }
84
90 {
91 $bad = '<script>alert(1)</script>Hallo';
92
93 $result = MigratedLanguageFileSync::databaseValues(
94 ['unmodified' => $bad, 'changed' => $bad, 'not_shipped_at_all' => $bad],
95 ['unmodified' => $bad, 'changed' => 'Anderer Shipped-Wert']
96 );
97
98 $this->assertStringNotContainsString('<script', $result['unmodified']);
99 $this->assertStringContainsString('Hallo', $result['unmodified']);
100 // "changed" differs from its shipped value - databaseValues() does not touch it at all
101 $this->assertSame($bad, $result['changed']);
102 // no shipped counterpart at all - left untouched, too
103 $this->assertSame($bad, $result['not_shipped_at_all']);
104 }
105
107 {
108 $result = MigratedLanguageFileSync::databaseValues(['greeting' => 'Hallo'], ['greeting' => 'Hallo']);
109
110 $this->assertSame('Hallo', $result['greeting']);
111 }
112
114 {
115 $bad = '<script>alert(1)</script>Hallo';
116 $this->seedShipped(['greeting' => $bad]);
117
118 $result = MigratedLanguageFileSync::databaseValuesOf(
119 $this->manager,
120 $this->ilias_root,
121 'de',
122 self::MODULE,
123 ['greeting' => $bad]
124 );
125
126 $this->assertStringNotContainsString('<script', $result['greeting']);
127 $this->assertStringContainsString('Hallo', $result['greeting']);
128 }
129
132 {
133 $bad = '<script>alert(1)</script>Hallo';
134
135 $result = MigratedLanguageFileSync::databaseValuesOf(
136 $this->manager,
137 $this->ilias_root,
138 'de',
139 'not_a_contributed_module',
140 ['greeting' => $bad]
141 );
142
143 $this->assertSame($bad, $result['greeting']);
144 }
145
151 {
152 file_put_contents($this->shippedPo(), "msgid \"kaputt\n");
153 $bad = '<script>alert(1)</script>Hallo';
154
155 $result = MigratedLanguageFileSync::databaseValuesOf(
156 $this->manager,
157 $this->ilias_root,
158 'de',
159 self::MODULE,
160 ['greeting' => $bad]
161 );
162
163 $this->assertSame($bad, $result['greeting']);
164 }
165
167 {
168 $this->seedShipped(['greeting' => 'Hallo']);
169 $this->seedOverlay(['greeting' => 'Servus']);
170
171 $this->assertTrue(MigratedLanguageFileSync::hasOverlay($this->manager, 'de', self::MODULE, $this->client_data_dir));
172 }
173
175 {
176 $this->seedShipped(['greeting' => 'Hallo']);
177
178 $this->assertFalse(MigratedLanguageFileSync::hasOverlay($this->manager, 'de', self::MODULE, $this->client_data_dir));
179 }
180
182 {
183 $this->seedShipped(['greeting' => 'Hallo']);
184 $this->seedOverlay(['greeting' => 'Servus']);
185
186 $this->assertFalse(MigratedLanguageFileSync::hasOverlay($this->manager, 'de', 'other_module', $this->client_data_dir));
187 $this->assertFalse(MigratedLanguageFileSync::hasOverlay($this->manager, 'de', self::MODULE, null));
188 }
189
195 {
196 $this->seedShipped(['greeting' => 'Hallo']);
197 mkdir($this->overlayBase() . '.po', 0775, true);
198
199 $this->assertTrue(MigratedLanguageFileSync::hasOverlay($this->manager, 'de', self::MODULE, $this->client_data_dir));
200 }
201
204 {
205 $catalog = new TranslationCatalog();
206 $catalog->add(MigratedPoFixture::entry(null, 'greeting', 'Hallo'));
207
208 $entries = MigratedLanguageFileSync::moduleEntries($catalog, 'itest');
209
210 $this->assertArrayHasKey('greeting', $entries);
211 $this->assertSame('Hallo', $entries['greeting']->getTranslation());
212 }
213
215 #[DataProvider('foreignContexts')]
216 public function testModuleEntriesIgnoresAnEntryOfAForeignContext(string $context): void
217 {
218 $catalog = new TranslationCatalog();
219 $catalog->add(MigratedPoFixture::entry($context, 'greeting', 'Fremd'));
220
221 $entries = MigratedLanguageFileSync::moduleEntries($catalog, 'itest');
222
223 $this->assertArrayNotHasKey('greeting', $entries);
224 }
225
229 public static function foreignContexts(): array
230 {
231 return ['empty-string msgctxt' => [''], 'context of another module' => ['other_module']];
232 }
233
235 #[DataProvider('contextOrders')]
237 {
238 $catalog = new TranslationCatalog();
239 $module_entry = MigratedPoFixture::entry('itest', 'greeting', 'Mit Modulkontext');
240 $contextless = MigratedPoFixture::entry(null, 'greeting', 'Ohne Kontext');
241 foreach ($module_first ? [$module_entry, $contextless] : [$contextless, $module_entry] as $entry) {
242 $catalog->add($entry);
243 }
244
245 $entries = MigratedLanguageFileSync::moduleEntries($catalog, 'itest');
246
247 $this->assertSame('Mit Modulkontext', $entries['greeting']->getTranslation());
248 }
249
253 public static function contextOrders(): array
254 {
255 return ['module context first' => [true], 'contextless first' => [false]];
256 }
257
258
260 {
261 $catalog = new TranslationCatalog();
262 $catalog->add(MigratedPoFixture::entry(null, 'greeting', 'Hallo'));
263
264 $entry = MigratedLanguageFileSync::findModuleEntry($catalog, 'itest', 'greeting');
265
266 $this->assertNotNull($entry);
267 $this->assertSame('Hallo', $entry->getTranslation());
268 }
269
271 {
272 $catalog = new TranslationCatalog();
273 $catalog->add(MigratedPoFixture::entry(null, 'greeting', 'Ohne Kontext'));
274 $catalog->add(MigratedPoFixture::entry('itest', 'greeting', 'Mit Modulkontext'));
275
276 $entry = MigratedLanguageFileSync::findModuleEntry($catalog, 'itest', 'greeting');
277
278 $this->assertSame('Mit Modulkontext', $entry?->getTranslation());
279 }
280
282 {
283 $catalog = new TranslationCatalog();
284 $catalog->add(MigratedPoFixture::entry('other_module', 'greeting', 'Fremd'));
285
286 $this->assertNull(MigratedLanguageFileSync::findModuleEntry($catalog, 'itest', 'greeting'));
287 }
288
295 {
296 $catalog = new TranslationCatalog();
297 $catalog->add(MigratedPoFixture::entry('', 'farewell', 'Leer'));
298
299 $this->assertNull(MigratedLanguageFileSync::findModuleEntry($catalog, 'itest', 'farewell'));
300 }
301
304 {
305 $catalog = new TranslationCatalog();
306 $catalog->add(MigratedPoFixture::entry(null, 'greeting', 'Hallo'));
307 MigratedPoFixture::writePo($this->shippedPo(), $catalog);
308
309 $entries = MigratedLanguageFileSync::loadShippedModuleEntries($this->shippedPo(), self::MODULE);
310
311 $this->assertSame('Hallo', $entries['greeting']['value']);
312 }
313
319 {
320 $catalog = new TranslationCatalog();
321 $catalog->add(MigratedPoFixture::entry(null, 'greeting', 'Hallo'));
322 MigratedPoFixture::writePo($this->shippedPo(), $catalog);
323
324 $this->sync(['greeting' => 'Hallo']);
325
326 $this->assertFileDoesNotExist($this->overlayBase() . '.po', 'unchanged from the contextless shipped value - no delta at all');
327 }
328}
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
Tracks per overlay entry whether the current value still matches the value of the shipped ....
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
Entry rules of MigratedLanguageFileSync: delta/reset decisions, database sanitising,...
testSyncTreatsAContextlessShippedEntryAsThisModulesShippedValue()
End-to-end: sync() reads a contextless shipped entry as the module's shipped value,...
testModuleEntriesIncludesAnEntryWithoutAnyContext()
An entry without msgctxt (null) belongs to whichever module reads the catalog.
testFindModuleEntryIgnoresAnEmptyStringContextInsteadOfTreatingItAsContextless()
BUG (documented, not fixed): unlike moduleEntries(), findModuleEntry()'s fallback $catalog->find(null...
testLoadShippedModuleEntriesIncludesAContextlessEntry()
End-to-end: a contextless shipped entry is served for the module via loadShippedModuleEntries().
testDatabaseValuesSanitizesOnlyAnUnmodifiedValueEqualToTheShippedOne()
databaseValues() sanitises only an unmodified value equal to the shipped one (for the database mirror...
testDatabaseValuesOfLeavesEntriesUnchangedWhenTheShippedPoCannotBeRead()
An unreadable shipped .po must not abort the database write: $entries come back unchanged (the overla...
testBelongsInDelta(?string $shipped_value, string $value, bool $expected)
belongsInDelta() alone decides what enters the overlay; tested without sync()'s file I/O.
testHasOverlayIsTrueEvenForAnIrregularFileAtTheOverlayPosition()
hasOverlay() reports a non-regular file (e.g.
testModuleEntriesPrefersTheModuleContextEntryOverTheContextlessOne(bool $module_first)
A key with and without context: the module-context entry wins regardless of catalog order.
testResolveLocalValue(?string $shipped_value, string $value, string $expected)
Shared by belongsInDelta() and ilObjLanguageExt::_saveValues(): an empty value resets to the shipped ...
testModuleEntriesIgnoresAnEntryOfAForeignContext(string $context)
An entry with an empty msgctxt is a foreign context, not "no context".
testDatabaseValuesOfLeavesEntriesUnchangedWhenTheModuleIsNotMigratedForTheLanguage()
A module not migrated for the language (no shipped .po): $entries come back unchanged.
Fixtures of the MigratedLanguageFileSync tests.
sync(array $entries, bool $refresh=false, ?string $client_data_dir='default')
seedOverlay(array $entries, string $lang_key='de')
seedShipped(array $entries, string $lang_key='de', string $module=self::MODULE)
shippedPo(string $lang_key='de', string $module=self::MODULE)
static writePo(string $file, TranslationCatalog $catalog)
static entry(?string $context, string $id, string $value)
A directory of language files with a path and a prefix for them.