ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
LanguageInstallationManagerMigratedTakeOverTest.php
Go to the documentation of this file.
1<?php
2
20declare(strict_types=1);
21
25use PHPUnit\Framework\Attributes\DataProvider;
26
33{
37 private function takenOver(): array
38 {
39 return MigratedLanguageFileSync::modulesWithLocalChangesTakenOver($this->root . '/client-data', 'de');
40 }
41
42 private function takenOverFile(): string
43 {
44 return MigratedLanguageFilePaths::takenOverFile($this->root . '/client-data', 'de');
45 }
46
47 private function corruptOverlay(): void
48 {
49 file_put_contents($this->overlayBase() . '.po', "msgid \"kaputt\n");
50 }
51
53 {
54 $this->shipPo(['greeting' => 'Hallo']);
55 $this->db_local_changes = [self::MODULE => ['greeting' => 'Eigen']];
56
57 $this->manager()->insertLanguageForInstallation('de');
58
59 $this->assertSame('Eigen', $this->overlayPo()->find(null, 'greeting')->getTranslation());
60 $this->assertSame([self::MODULE], $this->takenOver());
61
62 $this->db_local_changes = [self::MODULE => ['greeting' => 'Danach in lng_data geändert']];
63 $this->manager()->insertLanguageForInstallation('de');
64
65 $this->assertSame('Eigen', $this->effective()['greeting']['value'], 'lng_data is no source any more');
66 }
67
70 {
71 $this->shipPo(['greeting' => 'Hallo']);
72 $this->manager()->insertLanguageForInstallation('de');
73 $this->assertSame([self::MODULE], $this->takenOver());
74 $this->assertFileDoesNotExist($this->overlayBase() . '.po');
75
76 $this->db_local_changes = [self::MODULE => ['greeting' => 'Später in lng_data']];
77 $this->manager()->insertLanguageForInstallation('de');
78
79 $this->assertSame('Hallo', $this->effective()['greeting']['value']);
80 $this->assertFileDoesNotExist($this->overlayBase() . '.po');
81 }
82
84 {
85 $this->shipPo(['greeting' => 'Hallo']);
86 $this->seedOverlay(['greeting' => ['value' => 'Overlay', 'original' => 'Hallo', 'local_change' => '2025-05-05T05:05:05Z']]);
87 $this->db_local_changes = [self::MODULE => ['greeting' => 'Datenbank']];
88
89 $this->manager()->insertLanguageForInstallation('de');
90
91 $this->assertSame('Overlay', $this->effective()['greeting']['value']);
92 $this->assertSame([self::MODULE], $this->takenOver());
93 }
94
96 {
97 $this->shipPo(['greeting' => 'Hallo']);
98 $this->db_local_changes = [self::MODULE => ['greeting' => 'Eigen']];
99
100 $this->manager(static fn(): ?string => null)->insertLanguageForInstallation('de');
101
102 $this->assertSame('Eigen', $this->lngModules()[self::MODULE]['greeting']);
103 $this->assertFileDoesNotExist($this->takenOverFile());
104 $this->assertDirectoryDoesNotExist($this->root . '/client-data/lang');
105 $this->assertSame('', $this->loggedWarnings(), 'not even a failed attempt to record');
106 }
107
110 {
111 $this->expectManagerWarning();
112 $this->shipPo(['greeting' => 'Hallo']);
113 $this->shipSecondModule(['farewell' => 'Tschüss']);
114 mkdir($this->root . '/client-data/lang', 0775, true);
115 file_put_contents($this->root . '/client-data/lang/pilot', 'not a directory');
116 $this->db_local_changes = [self::MODULE => ['greeting' => 'Eigen']];
117
118 $unwritten = $this->manager()->insertLanguageForInstallation('de');
119
120 $this->assertSame([self::MODULE], $unwritten);
121 $this->assertSame(['pilot2'], $this->takenOver());
122 }
123
127 public static function unusableRecords(): array
128 {
129 return [
130 'not JSON' => ['{kaputt'],
131 'an object' => ['{"a":"pilot"}'],
132 'only invalid entries' => ['["Bad Name", 5, "../x"]'],
133 ];
134 }
135
136 #[DataProvider('unusableRecords')]
138 {
139 $this->shipPo(['greeting' => 'Hallo']);
140 mkdir($this->root . '/client-data/lang', 0775, true);
141 file_put_contents($this->takenOverFile(), $record);
142 $this->assertSame([], $this->takenOver());
143 $this->db_local_changes = [self::MODULE => ['greeting' => 'Eigen']];
144
145 $this->manager()->insertLanguageForInstallation('de');
146
147 $this->assertSame('Eigen', $this->effective()['greeting']['value']);
148 $this->assertSame([self::MODULE], $this->takenOver());
149 }
150
152 {
153 $this->shipPo(['greeting' => 'Hallo']);
154 $this->db_local_changes = [self::MODULE => ['greeting' => 'Eigen']];
155 $this->manager()->insertLanguageForInstallation('de');
156 $this->assertFileExists($this->takenOverFile());
157
158 $this->available_languages = ['de' => ['obj_id' => 3, 'status' => 'installed']];
159 $this->manager()->installLanguages([]);
160
161 $this->assertFileDoesNotExist($this->takenOverFile());
162 $this->assertFileDoesNotExist($this->overlayBase() . '.po');
163
164 $this->db_local_changes = [self::MODULE => ['greeting' => 'Wieder in lng_data']];
165 $this->manager()->insertLanguageForInstallation('de');
166
167 $this->assertSame('Wieder in lng_data', $this->effective()['greeting']['value']);
168 }
169
171 {
172 $this->writeLang('lang/ilias_de.lang', ['common#:#yes#:#Ja']);
173 $this->shipPo(['greeting' => 'Hallo']);
174 $this->db_local_changes = ['common' => ['yes' => 'Jawohl']];
175
176 $this->manager()->insertLanguageForInstallation('de');
177
178 $this->assertSame('Jawohl', $this->lngModules()['common']['yes']);
179 $this->assertSame([self::MODULE], $this->takenOver());
180 }
181
185 public static function overlayDates(): array
186 {
187 return [
188 'newer than the customizing file' => ['2030-01-01T00:00:00Z', 'Overlay'],
189 'older than the customizing file' => ['2000-01-01T00:00:00Z', 'Customizing'],
190 'without a usable date' => ['kein Datum', 'Overlay'],
191 ];
192 }
193
195 #[DataProvider('overlayDates')]
196 public function testALocalChangeOfTheOverlayOverrulesTheCustomizingFileUnlessItIsOlder(string $date, string $expected): void
197 {
198 $this->shipPo(['greeting' => 'Hallo']);
199 $this->seedOverlay(['greeting' => ['value' => 'Overlay', 'original' => 'Hallo', 'local_change' => $date]]);
200 $this->writeLang('lang/customizing/ilias_de.lang.local', ['pilot#:#greeting#:#Customizing']);
201
202 $this->manager()->insertLanguageForInstallation('de');
203
204 $this->assertSame($expected, $this->effective()['greeting']['value']);
205 }
206
209 {
210 $this->expectManagerWarning();
211 $this->shipPo(['greeting' => 'Hallo']);
212 $this->shipSecondModule(['farewell' => 'Tschüss']);
213 $this->writeLang('lang/customizing/ilias_de.lang.local', ['pilot#:#greeting#:#<script>evil</script>']);
214 $manager = $this->manager();
215
216 $manager->insertLanguageForInstallation('de');
217
218 $this->assertSame(['pilot2'], $this->takenOver());
219 $this->assertArrayHasKey('pilot#:#greeting', $manager->getSkippedInvalidMarkupEntries()['de'] ?? []);
220
221 $manager->insertLanguageForInstallation('de');
222
223 $this->assertArrayHasKey('pilot#:#greeting', $manager->getSkippedInvalidMarkupEntries()['de'] ?? [], 'reported again');
224 $this->assertSame(['pilot2'], $this->takenOver());
225
226 $this->writeLang('lang/customizing/ilias_de.lang.local', ['pilot#:#greeting#:#Jetzt gültig']);
227 $manager->insertLanguageForInstallation('de');
228
229 $this->assertSame([self::MODULE, 'pilot2'], $this->takenOver());
230 }
231
234 {
235 $this->expectManagerWarning();
236 $this->shipPo(['greeting' => 'Hallo']);
237 $this->shipSecondModule(['farewell' => 'Tschüss']);
238 $this->seedOverlay(['greeting' => ['value' => 'Overlay', 'original' => 'Hallo', 'local_change' => '2025-05-05T05:05:05Z']]);
239 $this->corruptOverlay();
240 $this->writeLang('lang/customizing/ilias_de.lang.local', ['pilot#:#greeting#:#Customizing', 'pilot2#:#farewell#:#Pfiat di']);
241
242 $manager = $this->manager();
243 $manager->insertLanguageForInstallation('de');
244
245 $this->assertSame(['de' => [self::MODULE]], $manager->getSkippedUnreadableOverlays());
246 $this->assertArrayNotHasKey(self::MODULE, $this->lngModules());
247 $this->assertArrayNotHasKey('pilot|greeting', $this->lngData());
248 $this->assertSame("msgid \"kaputt\n", file_get_contents($this->overlayBase() . '.po'));
249 $this->assertStringContainsString('rollback copy in lng_data/lng_modules is incomplete', $this->loggedWarnings());
250 $this->assertSame('Pfiat di', $this->lngModules()['pilot2']['farewell']);
251 $this->assertSame(['pilot2'], $this->takenOver());
252 }
253
256 {
257 $this->expectManagerWarning();
258 $this->shipPo(['greeting' => 'Hallo']);
259 $this->seedOverlay(['greeting' => ['value' => 'Overlay', 'original' => 'Hallo', 'local_change' => '2025-05-05T05:05:05Z']]);
260 $this->corruptOverlay();
261 $manager = $this->manager();
262 $manager->insertLanguageForInstallation('de');
263 $this->assertSame(['de' => [self::MODULE]], $manager->getSkippedUnreadableOverlays());
264
265 $this->seedOverlay(['greeting' => ['value' => 'Overlay', 'original' => 'Hallo', 'local_change' => '2025-05-05T05:05:05Z']]);
266 $manager->insertLanguageForInstallation('de');
267
268 $this->assertSame([], $manager->getSkippedUnreadableOverlays());
269 $this->assertSame('Overlay', $this->lngModules()[self::MODULE]['greeting']);
270 $this->assertSame([self::MODULE], $this->takenOver());
271 }
272}
Tracks per overlay entry whether the current value still matches the value of the shipped ....
The single place that knows where the files of a module migrated to PO/MO live:
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
Fixtures of the LanguageInstallationManager tests for modules migrated to PO/MO ("pilot").
effective()
Shipped .po plus overlay delta, as served (MigratedLanguageFileSync::loadModuleTranslations()) - the ...
shipSecondModule(array $entries)
A second migrated module "pilot2" next to "pilot".
manager(?\Closure $client_data_dir_resolver=null, ?\Closure $language_cache_invalidator=null, bool $with_resolver=true, bool $with_logger=true, ?LoggerInterface $logger=null)
The lng_data local changes of a migrated module are taken over into its overlay once,...
testAfterTheRepairTheNextRunInstallsTheModuleAndEmptiesTheSkipList()
Repaired, the same manager installs the module normally and no longer reports it.
testAModuleRecordedWithoutAnOverlayNeverReadsLngDataAgain()
Taken over means "from now on only the overlay": also if there was nothing to take over and so no ove...
testAModuleWhoseOverlayCouldNotBeWrittenIsNotRecorded()
A file where the overlay directory must be: the sync fails, so the rows must be taken over again next...
testALocalChangeOfTheOverlayOverrulesTheCustomizingFileUnlessItIsOlder(string $date, string $expected)
The overlay is the only source, so its local change overrules the customizing file unless it is older...
testAModuleWithAMarkupRejectedCustomizingEntryIsNotRecordedUntilItIsFixed()
A module with a customizing entry left out for its markup is not recorded: it is reported again and r...
testAModuleWithAnUnreadableOverlayIsLeftOutAndTheOthersAreInstalledNormally()
An unreadable overlay: nothing of the module is written - no lng_data, no lng_modules,...