20declare(strict_types=1);
25use PHPUnit\Framework\Attributes\DataProvider;
39 return MigratedLanguageFileSync::modulesWithLocalChangesTakenOver($this->root .
'/client-data',
'de');
44 return MigratedLanguageFilePaths::takenOverFile($this->root .
'/client-data',
'de');
49 file_put_contents($this->
overlayBase() .
'.po',
"msgid \"kaputt\n");
54 $this->
shipPo([
'greeting' =>
'Hallo']);
55 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Eigen']];
57 $this->
manager()->insertLanguageForInstallation(
'de');
59 $this->assertSame(
'Eigen', $this->
overlayPo()->find(
null,
'greeting')->getTranslation());
60 $this->assertSame([self::MODULE], $this->
takenOver());
62 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Danach in lng_data geändert']];
63 $this->
manager()->insertLanguageForInstallation(
'de');
65 $this->assertSame(
'Eigen', $this->
effective()[
'greeting'][
'value'],
'lng_data is no source any more');
71 $this->
shipPo([
'greeting' =>
'Hallo']);
72 $this->
manager()->insertLanguageForInstallation(
'de');
73 $this->assertSame([self::MODULE], $this->
takenOver());
74 $this->assertFileDoesNotExist($this->
overlayBase() .
'.po');
76 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Später in lng_data']];
77 $this->
manager()->insertLanguageForInstallation(
'de');
79 $this->assertSame(
'Hallo', $this->
effective()[
'greeting'][
'value']);
80 $this->assertFileDoesNotExist($this->
overlayBase() .
'.po');
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']];
89 $this->
manager()->insertLanguageForInstallation(
'de');
91 $this->assertSame(
'Overlay', $this->
effective()[
'greeting'][
'value']);
92 $this->assertSame([self::MODULE], $this->
takenOver());
97 $this->
shipPo([
'greeting' =>
'Hallo']);
98 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Eigen']];
100 $this->
manager(
static fn(): ?
string =>
null)->insertLanguageForInstallation(
'de');
102 $this->assertSame(
'Eigen', $this->
lngModules()[self::MODULE][
'greeting']);
104 $this->assertDirectoryDoesNotExist($this->root .
'/client-data/lang');
105 $this->assertSame(
'', $this->
loggedWarnings(),
'not even a failed attempt to record');
112 $this->
shipPo([
'greeting' =>
'Hallo']);
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']];
118 $unwritten = $this->
manager()->insertLanguageForInstallation(
'de');
120 $this->assertSame([self::MODULE], $unwritten);
121 $this->assertSame([
'pilot2'], $this->
takenOver());
130 'not JSON' => [
'{kaputt'],
131 'an object' => [
'{"a":"pilot"}'],
132 'only invalid entries' => [
'["Bad Name", 5, "../x"]'],
136 #[DataProvider('unusableRecords')]
139 $this->
shipPo([
'greeting' =>
'Hallo']);
140 mkdir($this->root .
'/client-data/lang', 0775,
true);
142 $this->assertSame([], $this->
takenOver());
143 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Eigen']];
145 $this->
manager()->insertLanguageForInstallation(
'de');
147 $this->assertSame(
'Eigen', $this->
effective()[
'greeting'][
'value']);
148 $this->assertSame([self::MODULE], $this->
takenOver());
153 $this->
shipPo([
'greeting' =>
'Hallo']);
154 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Eigen']];
155 $this->
manager()->insertLanguageForInstallation(
'de');
158 $this->available_languages = [
'de' => [
'obj_id' => 3,
'status' =>
'installed']];
159 $this->
manager()->installLanguages([]);
162 $this->assertFileDoesNotExist($this->
overlayBase() .
'.po');
164 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Wieder in lng_data']];
165 $this->
manager()->insertLanguageForInstallation(
'de');
167 $this->assertSame(
'Wieder in lng_data', $this->
effective()[
'greeting'][
'value']);
172 $this->
writeLang(
'lang/ilias_de.lang', [
'common#:#yes#:#Ja']);
173 $this->
shipPo([
'greeting' =>
'Hallo']);
174 $this->db_local_changes = [
'common' => [
'yes' =>
'Jawohl']];
176 $this->
manager()->insertLanguageForInstallation(
'de');
178 $this->assertSame(
'Jawohl', $this->
lngModules()[
'common'][
'yes']);
179 $this->assertSame([self::MODULE], $this->
takenOver());
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'],
195 #[DataProvider('overlayDates')]
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']);
202 $this->
manager()->insertLanguageForInstallation(
'de');
204 $this->assertSame($expected, $this->
effective()[
'greeting'][
'value']);
211 $this->
shipPo([
'greeting' =>
'Hallo']);
213 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#<script>evil</script>']);
216 $manager->insertLanguageForInstallation(
'de');
218 $this->assertSame([
'pilot2'], $this->
takenOver());
219 $this->assertArrayHasKey(
'pilot#:#greeting', $manager->getSkippedInvalidMarkupEntries()[
'de'] ?? []);
221 $manager->insertLanguageForInstallation(
'de');
223 $this->assertArrayHasKey(
'pilot#:#greeting', $manager->getSkippedInvalidMarkupEntries()[
'de'] ?? [],
'reported again');
224 $this->assertSame([
'pilot2'], $this->
takenOver());
226 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#Jetzt gültig']);
227 $manager->insertLanguageForInstallation(
'de');
229 $this->assertSame([self::MODULE,
'pilot2'], $this->
takenOver());
236 $this->
shipPo([
'greeting' =>
'Hallo']);
238 $this->
seedOverlay([
'greeting' => [
'value' =>
'Overlay',
'original' =>
'Hallo',
'local_change' =>
'2025-05-05T05:05:05Z']]);
240 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#Customizing',
'pilot2#:#farewell#:#Pfiat di']);
243 $manager->insertLanguageForInstallation(
'de');
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());
258 $this->
shipPo([
'greeting' =>
'Hallo']);
259 $this->
seedOverlay([
'greeting' => [
'value' =>
'Overlay',
'original' =>
'Hallo',
'local_change' =>
'2025-05-05T05:05:05Z']]);
262 $manager->insertLanguageForInstallation(
'de');
263 $this->assertSame([
'de' => [self::MODULE]], $manager->getSkippedUnreadableOverlays());
265 $this->
seedOverlay([
'greeting' => [
'value' =>
'Overlay',
'original' =>
'Hallo',
'local_change' =>
'2025-05-05T05:05:05Z']]);
266 $manager->insertLanguageForInstallation(
'de');
268 $this->assertSame([], $manager->getSkippedUnreadableOverlays());
269 $this->assertSame(
'Overlay', $this->
lngModules()[self::MODULE][
'greeting']);
270 $this->assertSame([self::MODULE], $this->
takenOver());
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").
shipPo(array $entries, string $lang_key='de')
seedOverlay(array $entries)
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)
writeLang(string $relative_file, array $lines)
The lng_data local changes of a migrated module are taken over into its overlay once,...
testAModuleOfNoMigrationIsStillReadFromLngDataAndNotRecorded()
testWithoutAClientDataDirectoryLngDataIsUsedAsBeforeAndNothingIsRecorded()
testAReadableOverlayWithoutTheRecordWinsOverLngData()
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...
testAnUnusableRecordCountsAsEmptyAndTheRowsAreTakenOverAgain(string $record)
testAModuleWithAMarkupRejectedCustomizingEntryIsNotRecordedUntilItIsFixed()
A module with a customizing entry left out for its markup is not recorded: it is reported again and r...
testTheLngDataLocalChangesAreTakenOverOnceAndNeverReadAgain()
testUninstallingTheLanguageForgetsTheRecordAndAReinstallTakesTheRowsOverAgain()
testAModuleWithAnUnreadableOverlayIsLeftOutAndTheOthersAreInstalledNormally()
An unreadable overlay: nothing of the module is written - no lng_data, no lng_modules,...