19declare(strict_types=1);
31use PHPUnit\Framework\Attributes\DataProvider;
32use PHPUnit\Framework\TestCase;
38 $this->
shipPo([
'greeting' =>
'Hallo']);
39 $this->collation_rows = [
40 [
'module' =>
'common',
'lang_array' => serialize([
'yes' =>
'Ja'])],
41 [
'module' =>
self::MODULE,
'lang_array' =>
'a:1:{s:8:"greeting";s:5:"Hal'],
47 $this->
manager(
null,
static function (
string $lang_key) use (&$invalidated):
void {
48 $invalidated[] = $lang_key;
49 })->insertLanguageForInstallation(
'de');
50 $this->fail(
'Expected a LanguageDataNotSavedException');
52 $this->assertSame(409,
$e->getCode());
53 $this->assertStringContainsString(
"'pilot'",
$e->getMessage());
54 $this->assertStringContainsString(
"'de'",
$e->getMessage());
57 $this->assertSame([], $invalidated,
'no cache invalidation for data that was not saved correctly');
58 $this->assertFileDoesNotExist($this->
overlayBase() .
'.po',
'no overlay for data that was not saved correctly');
59 $this->assertSame($cwd, getcwd(),
'the working directory is restored on the exception path');
61 $collation_query = array_values(array_filter(
63 static fn(
string $q):
bool => str_starts_with($q,
'SELECT') && str_contains($q,
'lang_array') && str_contains($q,
'lng_modules')
65 $this->assertCount(1, $collation_query);
66 $this->assertStringContainsString(
"module IN ('pilot')", $collation_query[0]);
71 $this->
writeLang(
'lang/ilias_de.lang', [
'common#:#yes#:#Ja']);
72 $this->collation_rows = [[
'module' =>
'common',
'lang_array' => serialize(
false)]];
74 $this->expectException(LanguageDataNotSavedException::class);
75 $this->
manager()->insertLanguageForInstallation(
'de');
80 $this->
shipPo([
'greeting' =>
'Hallo']);
84 $this->
manager(
null,
function (
string $lang_key) use (&$calls, $overlay_po):
void {
85 $calls[] = [$lang_key, count(array_filter($this->queries,
static fn(
string $q):
bool => str_starts_with($q,
'INSERT INTO lng_modules'))), is_file($overlay_po)];
86 })->insertLanguageForInstallation(
'de');
88 $this->assertSame([[
'de', 1,
false]], $calls,
'once, after the lng_modules write, before the overlay sync');
94 $this->
shipPo([
'greeting' =>
'Hallo']);
95 $this->
seedOverlay([
'greeting' => [
'value' =>
'Hallo',
'original' =>
'Hallo']]);
97 $this->
manager(
null,
static function ():
void {
98 throw new RuntimeException(
'cache is down');
99 })->insertLanguageForInstallation(
'de');
101 $this->assertSame([
'greeting' =>
'Hallo'], $this->
lngModules()[self::MODULE]);
104 $this->assertStringContainsString(
'cache is down', $this->
loggedWarnings());
109 $this->expectErrorLog();
110 $this->
shipPo([
'greeting' =>
'Hallo']);
112 $this->
manager(
null,
static function ():
void {
113 throw new RuntimeException(
'cache is down');
114 },
true,
false)->insertLanguageForInstallation(
'de');
116 $this->assertStringContainsString(
'cache is down', $this->
errorLog());
117 $this->assertSame([], $this->logged_warnings);
122 $this->expectErrorLog();
123 $this->
shipPo([
'greeting' =>
'Hallo']);
124 $logger = $this->createStub(\
ILIAS\Logging\Logger\LoggerInterface::class);
125 $logger->method(
'warning')->willThrowException(
new RuntimeException(
'logger is down'));
127 $this->
manager(
null,
static function ():
void {
128 throw new RuntimeException(
'cache is down');
129 },
true,
true,
$logger)->insertLanguageForInstallation(
'de');
131 $this->assertSame([
'greeting' =>
'Hallo'], $this->
lngModules()[self::MODULE]);
132 $this->assertStringContainsString(
'cache is down', $this->
errorLog());
142 $this->
shipPo([
'greeting' =>
'Hallo']);
144 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Servus']];
145 rmdir($this->root .
'/client-data');
146 mkdir($this->root .
'/client-data');
147 file_put_contents($this->root .
'/client-data/lang',
'not a directory');
149 set_error_handler(
static fn():
bool =>
true, E_WARNING);
151 $unwritten = $this->
manager()->insertLanguageForInstallation(
'de');
153 restore_error_handler();
156 $this->assertSame([
'greeting' =>
'Servus'], $this->
lngModules()[self::MODULE]);
157 $this->assertStringContainsString(
'Could not sync migrated language file for module "pilot"', $this->
loggedWarnings());
158 $this->assertSame([self::MODULE], $unwritten,
'the failed module is returned so callers can report it');
165 #[DataProvider('writePaths')]
168 if ($writes_overlay) {
171 $this->
shipPo([
'greeting' =>
'Hallo']);
172 $this->
writeLang(
'lang/ilias_de.lang', [
'common#:#yes#:#Ja']);
173 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#Servus']);
174 $this->db_entries = [self::MODULE => [
'greeting' =>
'Hallo'],
'common' => [
'yes' =>
'Ja']];
175 file_put_contents($this->root .
'/client-data/lang',
'not a directory');
177 set_error_handler(
static fn():
bool =>
true, E_WARNING);
179 $unwritten = $this->
manager()->$method(
'de');
181 restore_error_handler();
185 $this->assertSame($writes_overlay ? [self::MODULE] : [], $unwritten);
188 #[DataProvider('writePaths')]
191 $this->
shipPo([
'greeting' =>
'Hallo']);
192 $this->
writeLang(
'lang/ilias_de.lang', [
'common#:#yes#:#Ja']);
193 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#Servus']);
194 $this->db_entries = [self::MODULE => [
'greeting' =>
'Hallo'],
'common' => [
'yes' =>
'Ja']];
196 $this->assertSame([], $this->
manager()->$method(
'de'));
207 'install/update' => [
'insertLanguageForInstallation',
true],
208 'remove local changes' => [
'insertLanguageForRemovingLocalChanges',
false],
209 'apply local changes' => [
'insertLanguageForApplyingLocalChanges',
true],
218 $this->
shipPo([
'greeting' =>
'Hallo']);
220 $this->assertSame([], $this->
manager(
static fn(): ?
string =>
null)->insertLanguageForInstallation(
'de'));
229 $this->
shipPo([
'greeting' =>
'Hallo']);
230 $blocked = $this->root .
'/blocked';
232 file_put_contents($blocked .
'/lang',
'not a directory');
233 $target = $this->root .
'/client-data';
234 $manager = $this->
manager(
static function () use (&
$target): ?
string {
238 $this->assertSame([], $manager->findUnwritableOverlayDirectories([
'de']));
241 $this->assertSame([$blocked .
'/lang/pilot/de'], $manager->findUnwritableOverlayDirectories([
'de']));
242 $this->assertSame([], $manager->findUnwritableOverlayDirectories([
'en']),
'pilot is not migrated for en');
245 $this->assertSame([], $manager->findUnwritableOverlayDirectories([
'de']));
250 $this->
shipPo([
'greeting' =>
'Hallo']);
251 $elsewhere = $this->root .
'/elsewhere';
254 $manager = $this->
manager(
static function () use (&
$target): ?
string {
260 $this->db_local_changes = [self::MODULE => [
'greeting' =>
'Servus']];
261 $manager->insertLanguageForInstallation(
'de');
264 $this->assertDirectoryDoesNotExist($this->root .
'/client-data/lang');
267 #[DataProvider('noClientDataDir')]
270 $this->
shipPo([
'greeting' =>
'Hallo']);
272 $this->
manager(
static fn(): ?
string =>
null,
null, $with_resolver)->insertLanguageForInstallation(
'de');
274 $this->assertSame([
'greeting' =>
'Hallo'], $this->
lngModules()[self::MODULE]);
275 $this->assertDirectoryDoesNotExist($this->root .
'/client-data/lang');
283 return [
'resolver returns null' => [
true],
'no resolver' => [
false]];
295 'common#:#yes#:#Doppelt',
296 'common#:#no#:#Nein',
299 $this->
manager()->insertLanguageForInstallation(
'de');
301 $this->assertSame([
'yes' =>
'Ja',
'no' =>
'Nein'], $this->
lngModules()[
'common']);
302 $this->assertSame([
'value' =>
'Ja',
'local_change' =>
null], $this->
lngData()[
'common|yes']);
303 $this->assertStringContainsString(
'Duplicate language entries for language "de"', $this->
loggedWarnings());
304 $this->assertStringContainsString(
'common#:#yes', $this->
loggedWarnings());
309 $this->
writeLang(
'lang/ilias_de.lang', [
'common#:#yes#:#Ja']);
310 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'common#:#yes#:#Jawohl']);
312 $this->
manager()->insertLanguageForInstallation(
'de');
314 $this->assertSame([
'yes' =>
'Jawohl'], $this->
lngModules()[
'common']);
315 $this->assertStringNotContainsString(
'Duplicate', $this->
loggedWarnings());
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
The module array just written to lng_modules could not be read back - almost always a wrong collation...
Write access to the language installation: installing, flushing and registering languages in the data...
testAModuleArrayThatCannotBeReadBackThrowsLanguageDataNotSaved()
testFindUnwritableOverlayDirectoriesChecksTheResolvedClientDataDir()
Checked against the client data directory the instance maintains the overlay in (resolved at call tim...
testEveryWritePathReturnsOnlyTheModulesWhoseOverlayCouldNotBeWritten(string $method, bool $writes_overlay)
The same failure is returned by all three write paths - and only the migrated module whose overlay fa...
testTheResolversClientDataDirIsUsedAndResolvedAtCallTime()
testWithoutALoggerAWarningGoesToThePhpErrorLog()
testTheSameEntryInTwoDifferentFilesIsNotADuplicate()
testADuplicateInOneFileKeepsTheFirstOccurrenceAndIsReported()
Shipped ilias_fa.lang/ilias_nl.lang contain duplicates: the first occurrence wins,...
testASerializedFalseIsAlsoDetectedAsNotSaved()
testWithoutClientDataDirNothingIsReturnedAsUnwritten()
No overlay is maintained without client data directory - that is not a failure to report.
testWithoutClientDataDirTheDatabaseIsWrittenButNoOverlay(bool $with_resolver)
testAThrowingLoggerFallsBackToThePhpErrorLogAndDoesNotAbortTheInstallation()
testEveryWritePathReturnsNothingWhenTheOverlayWasWritten(string $method, bool $writes_overlay)
testTheLanguageCacheIsInvalidatedWithTheLanguageKeyBeforeTheOverlayIsSynced()
testAnOverlaySyncFailureIsLoggedAndTheDatabaseWriteStays()
Root-proof overlay failure (a file where the overlay's "lang" directory must be created): logged,...
testAFailingCacheInvalidatorIsLoggedAndSwallowed()
Fixtures of the LanguageInstallationManager tests for modules migrated to PO/MO ("pilot").
shipPo(array $entries, string $lang_key='de')
seedOverlay(array $entries)
errorLog()
What error_log() wrote during this test so far - PHPUnit redirects error_log to a capture file per te...
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)
static overlayMo(string $base_path)
The compiled catalog of the revision current names; a non-existing path if there is none.
A directory of language files with a path and a prefix for them.
Read-only access to the installed languages (database) and the available or valid language files (fil...
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.