19declare(strict_types=1);
29 protected function setUp(): void
33 if (!defined(
'ILIAS_ABSOLUTE_PATH')) {
34 define(
'ILIAS_ABSOLUTE_PATH', realpath(__DIR__ .
'/../../../../'));
40 if (isset($this->plugin_root_directory) && is_dir($this->plugin_root_directory)) {
49 $this->plugin_root_directory = sys_get_temp_dir() .
'/ilias_plugin_lang_test_' . bin2hex(random_bytes(4));
50 mkdir($this->plugin_root_directory .
'/lang', 0775,
true);
52 $component = $this->createStub(ilComponentInfo::class);
53 $component->method(
'getId')->willReturn(
'comp');
54 $slot = $this->createStub(ilPluginSlotInfo::class);
55 $slot->method(
'getId')->willReturn(
'slot');
57 $plugin_info = $this->createStub(ilPluginInfo::class);
58 $plugin_info->method(
'getComponent')->willReturn($component);
59 $plugin_info->method(
'getPluginSlot')->willReturn($slot);
60 $plugin_info->method(
'getId')->willReturn($module_prefix);
61 $plugin_info->method(
'getPath')->willReturn($this->plugin_root_directory);
69 $catalog = new \ILIAS\Language\ComponentTranslation\Catalog\TranslationCatalog();
70 foreach ($entries as $identifier => $value) {
79 touch($this->plugin_root_directory .
'/lang/ilias_de.lang');
80 touch($this->plugin_root_directory .
'/lang/ilias_en.po');
82 $files = $plugin_language->getAvailableLangFiles();
85 $this->assertSame(
'ilias_de.lang',
$by_key[
'de']);
86 $this->assertSame(
'ilias_en.po',
$by_key[
'en']);
92 touch($this->plugin_root_directory .
'/lang/ilias_de.lang');
93 touch($this->plugin_root_directory .
'/lang/ilias_de.po');
95 $files = $plugin_language->getAvailableLangFiles();
97 $this->assertCount(1,
$files);
98 $this->assertSame(
'ilias_de.po',
$files[0][
'file']);
111 $self_check_statement = $this->createStub(ilDBStatement::class);
112 $no_rows_statement = $this->createStub(ilDBStatement::class);
113 $db = $this->createStub(ilDBInterface::class);
114 $db->method(
'quote')->willReturnCallback(
static fn($value,
string $type =
''):
string =>
"'" . (
string) $value .
"'");
115 $db->method(
'queryF')->willReturnCallback(
116 static fn(
string $sql) => str_contains($sql,
'lng_modules') ? $self_check_statement : $no_rows_statement
118 $db->method(
'query')->willReturn($self_check_statement);
119 $db->method(
'fetchAssoc')->willReturnCallback(
120 static fn(
ilDBStatement $statement): ?array => $statement === $self_check_statement
121 ? [
'lang_array' => serialize([])]
124 $db->method(
'manipulate')->willReturn(0);
125 $db->method(
'insert')->willReturn(1);
126 $db->method(
'replace')->willReturnCallback(
127 function (
string $table, array $keys, array $values) use (&$captured_replace_entries):
int {
128 $captured_replace_entries[$keys[
'module'][1] .
'#:#' . $keys[
'identifier'][1]] = $values[
'value'][1];
140 $logger = $this->createStub(ilLogger::class);
141 $logger->method(
'warning')->willReturnCallback(
static function (
string $message) use (&$warnings):
void {
144 $logger_factory = $this->createStub(ilLoggerFactory::class);
145 $logger_factory->method(
'getComponentLogger')->willReturn(
$logger);
156 $plugin_language->updateLanguages([
'de']);
159 $this->assertSame(
'Hallo', $captured[
'comp_slot_ptest#:#comp_slot_ptest_greeting']);
165 $context =
'some_module';
167 $catalog = new \ILIAS\Language\ComponentTranslation\Catalog\TranslationCatalog();
176 $plugin_language->updateLanguages([
'de']);
178 $this->assertArrayNotHasKey(
'comp_slot_ptest#:#comp_slot_ptest_foreign', $captured);
179 $this->assertSame(
'Hallo', $captured[
'comp_slot_ptest#:#comp_slot_ptest_greeting']);
180 $this->assertNotEmpty(array_filter($warnings,
static fn(
string $w):
bool => str_contains($w,
'msgctxt') && str_contains($w,
'foreign')));
193 $plugin_language->updateLanguages([
'de']);
195 $value = $captured[
'comp_slot_ptest#:#comp_slot_ptest_greeting'];
196 $this->assertStringNotContainsString(
'<script', $value);
197 $this->assertStringContainsString(
'Hallo', $value);
198 $this->assertNotEmpty(array_filter($warnings,
static fn(
string $w):
bool => str_contains($w,
'Markup not allowed')));
206 $this->plugin_root_directory .
'/lang/ilias_de.lang',
207 "greeting#:#<script>alert(1)</script>Hallo\n"
214 $plugin_language->updateLanguages([
'de']);
216 $value = $captured[
'comp_slot_ptest#:#comp_slot_ptest_greeting'];
217 $this->assertSame(
'<script>alert(1)</script>Hallo', $value);
218 $this->assertNotEmpty(array_filter($warnings,
static fn(
string $w):
bool => str_contains($w,
'Markup not allowed')));
228 $plugin_language->updateLanguages([
'de']);
230 $this->assertArrayHasKey(
'comp_slot_ptest#:#comp_slot_ptest_greeting', $captured);
231 $this->assertArrayNotHasKey(
'comp_slot_ptest#:#comp_slot_ptest_empty_one', $captured);
238 file_put_contents($this->plugin_root_directory .
'/lang/ilias_de.po',
"msgid \"kaputt\n");
245 $plugin_language->updateLanguages([
'de',
'en']);
247 $this->assertSame(
'Hello', $captured[
'comp_slot_ptest#:#comp_slot_ptest_greeting'],
'the valid "en" .po is still applied');
248 $this->assertNotEmpty(array_filter($warnings,
static fn(
string $w):
bool => str_contains($w,
'cannot be read')));
254 $lng = $this->createStub(ilLanguage::class);
255 $lng->method(
'txt')->willReturnCallback(
static fn(
string $key):
string => $key ===
'comp_slot_ptest_known' ?
'Bekannt' :
'-' . $key .
'-');
256 $lng->method(
'getDefaultLanguage')->willReturn(
'en');
258 $this->
setGlobalVariable(
'ilClientIniFile', $this->createStub(ilIniFile::class));
259 $statement = $this->createStub(ilDBStatement::class);
260 $db = $this->createStub(ilDBInterface::class);
261 $db->method(
'quote')->willReturnCallback(
static fn($value):
string =>
"'" . (
string) $value .
"'");
263 $db->method(
'query')->willReturnCallback(
static function (
string $sql) use (&$queries, $statement) {
269 $db->method(
'fetchAssoc')->willReturnCallback(
static function () use (&$queries): ?array {
270 return str_contains(end($queries),
"module = 'comp_slot_ptest' AND lang_key = 'en' AND identifier = 'comp_slot_ptest_missing'")
271 ? [
'value' =>
'Missing en']
276 $this->assertSame(
'Bekannt', $plugin_language->txt(
'known'));
277 $this->assertSame([], $queries,
'a key present in the user language is not looked up again');
278 $this->assertSame(
'Missing en', $plugin_language->txt(
'missing'));
279 $this->assertCount(1, $queries);
280 $this->assertSame(
'-comp_slot_ptest_none-', $plugin_language->txt(
'none'));
static writePo(string $file, TranslationCatalog $catalog)
static removeDirectory(string $directory)
static entry(?string $context, string $id, string $value)
Class ilLanguageBaseTest.
setGlobalVariable(string $name, $value)
Simple value class for information about a plugin.
ilPluginLanguage: .po takes precedence over .lang, and updateLanguages() reads a plugin's ilias_<lang...
testUpdateLanguagesWritesPoValuesWithThePrefixThroughTheDatabaseOnly()
createPluginInfo(string $module_prefix)
writeShippedPluginPo(string $lang_key, array $entries)
testABrokenPoSkipsOnlyThatLanguageAndOthersAreStillUpdated()
A broken .po skips only that language.
string $plugin_root_directory
stubLoggerCapturingWarnings(array &$warnings)
testEmptyTranslationsAreSkipped()
testEntriesWithAMsgctxtAreIgnoredAndWarnedAbout()
An entry with a msgctxt (even empty) is ignored with a warning: a plugin .po has exactly one implicit...
testTxtFallsBackToTheDefaultLanguageForAKeyMissingInTheUserLanguage()
testDisallowedMarkupIsSanitisedAndWarnedAboutNotRejected()
Disallowed markup in a plugin .po value is cleaned and warned about.
testGetAvailableLangFilesListsBothLangAndPoFiles()
testDisallowedMarkupInALangFileIsTakenOverUnchangedAndWarnedAbout()
Unlike a plugin .po, a .lang value is taken over unchanged: disallowed markup is only logged.
testPoWinsOverLangForTheSameLanguage()
stubDatabase(array &$captured_replace_entries)
ilDB double for updateLanguages(): no local changes, replaceLangEntry()'s replace() captured,...