ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
ShippedLanguageFilesCompiledObjectiveTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
27use PHPUnit\Framework\TestCase;
28
35{
37
38 private string $root;
39
40 protected function setUp(): void
41 {
42 // never the build of the installation, see MigratedPoFixture::resetRuntime()
44 $this->root = sys_get_temp_dir() . '/ilias_shipped_build_' . bin2hex(random_bytes(6));
45 mkdir($this->root, 0775, true);
46 }
47
48 protected function tearDown(): void
49 {
51 }
52
56 private function writeShippedPo(string $relative_path, string $module, string $lang_key, array $entries): void
57 {
58 $directory = $this->root . '/' . trim($relative_path, '/');
59 MigratedPoFixture::writePo($directory . '/' . $module . '_' . $lang_key . '.po', MigratedPoFixture::catalog($module, $entries));
60 }
61
62 private function directoryManager(\ILIAS\Language\ComponentTranslation\LanguageFileDirectory ...$directories): LanguageFileDirectoryManager
63 {
64 return new LanguageFileDirectoryManager(new \ILIAS\Language\ComponentTranslation\CustomizingLanguageFileDirectory(), ...$directories);
65 }
66
70 private function informer(array &$messages): AdminInteraction
71 {
72 $messages = [];
73 $io = $this->createStub(AdminInteraction::class);
74 $io->method('inform')->willReturnCallback(function (string $message) use (&$messages): void {
75 $messages[] = $message;
76 });
77
78 return $io;
79 }
80
81 private function achieve(LanguageFileDirectoryManager $manager, ?AdminInteraction $io = null): void
82 {
83 $objective = new ShippedLanguageFilesCompiledObjective($manager, $this->root);
84 $environment = $this->createStub(Environment::class);
85 $environment->method('getResource')->willReturn($io);
86 $objective->achieve($environment);
87 }
88
92 private function artifactPath(string $lang_key, string $module): string
93 {
94 $artifact_directory = MigratedLanguageFilePaths::shippedArtifactDirectory($this->root);
95 $build = ShippedTranslationsBuild::readPointer($artifact_directory)['build'] ?? null;
96 if ($build === null) {
97 return $artifact_directory . '/no-build.mo';
98 }
99
100 return MigratedLanguageFilePaths::buildMoFile(MigratedLanguageFilePaths::buildDirectory($artifact_directory, $build), $module, $lang_key);
101 }
102
104 {
105 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
106 $directory = MigratedPoFixture::directory('itest', 'itest/');
107
108 $this->achieve($this->directoryManager($directory));
109
110 $this->assertFileExists($this->artifactPath('de', 'itest'));
111 $this->assertSame(
112 ['greeting' => 'Hallo'],
113 MigratedPoFixture::readMo($this->artifactPath('de', 'itest'))
114 );
115 }
116
120 public function testSkipsADirectoryWithoutAPrefix(): void
121 {
122 $directory = new \ILIAS\Language\ComponentTranslation\MainLanguageFileDirectory();
123 mkdir($this->root . '/lang', 0775, true);
124
125 $messages = [];
126 $io = $this->informer($messages);
127 $this->achieve($this->directoryManager($directory), $io);
128
129 $this->assertStringContainsString('no migrated module, 0 compiled', $messages[array_key_last($messages)]);
130 }
131
136 {
137 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
138 $directory = MigratedPoFixture::directory('itest', 'itest/');
139 $manager = $this->directoryManager($directory);
140 $this->achieve($manager);
141 $first_mtime = filemtime($this->artifactPath('de', 'itest'));
142
143 $messages = [];
144 $io = $this->informer($messages);
145 $this->achieve($manager, $io);
146
147 $this->assertStringContainsString('unchanged, 0 compiled', $messages[array_key_last($messages)]);
148 $this->assertSame($first_mtime, filemtime($this->artifactPath('de', 'itest')));
149 }
150
156 {
157 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
158 $this->writeShippedPo('utest/', 'utest', 'de', ['greeting' => 'Servus']);
159 $manager = $this->directoryManager(
160 MigratedPoFixture::directory('itest', 'itest/'),
161 MigratedPoFixture::directory('utest', 'utest/')
162 );
163 $this->achieve($manager);
164 $previous = dirname($this->artifactPath('de', 'utest'), 3);
165
166 // change only "itest"'s shipped .po - the incremental decision is by content hash, not mtime
167 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo geändert']);
168 $messages = [];
169 $io = $this->informer($messages);
170 $this->achieve($manager, $io);
171
172 $this->assertStringContainsString('2 compiled', $messages[array_key_last($messages)]);
173 $this->assertNotSame($previous, dirname($this->artifactPath('de', 'utest'), 3));
174 $this->assertDirectoryExists($previous, 'the previous build is kept');
175 $this->assertSame(
176 ['greeting' => 'Hallo geändert'],
177 MigratedPoFixture::readMo($this->artifactPath('de', 'itest'))
178 );
179 }
180
185 {
186 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
187 $directory = MigratedPoFixture::directory('itest', 'itest/');
188 $manager = $this->directoryManager($directory);
189 $this->achieve($manager);
190 $this->assertFileExists($this->artifactPath('de', 'itest'));
191
192 unlink($this->root . '/itest/itest_de.po');
193 $messages = [];
194 $io = $this->informer($messages);
195 $this->achieve($manager, $io);
196
197 $this->assertFileDoesNotExist($this->artifactPath('de', 'itest'));
198 $this->assertStringContainsString('no migrated module, 0 compiled', $messages[array_key_last($messages)]);
199 }
200
205 {
206 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => '<script>alert(1)</script>Hallo']);
207 $directory = MigratedPoFixture::directory('itest', 'itest/');
208
209 $messages = [];
210 $io = $this->informer($messages);
211 $this->achieve($this->directoryManager($directory), $io);
212
213 $warning = implode("\n", $messages);
214 $this->assertStringContainsString('WARNING: Markup not allowed in a language value', $warning);
215 $this->assertStringContainsString('itest', $warning);
216 $this->assertStringContainsString('greeting', $warning);
217 $this->assertFileExists($this->artifactPath('de', 'itest'));
218 $translations = MigratedPoFixture::readMo($this->artifactPath('de', 'itest'));
219 $this->assertStringNotContainsString('<script', $translations['greeting']);
220 }
221
223 {
224 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => '<script>x</script>Hallo']);
225 $directory = MigratedPoFixture::directory('itest', 'itest/');
226 $manager = $this->directoryManager($directory);
227 $this->achieve($manager);
228
229 $messages = [];
230 $io = $this->informer($messages);
231 $this->achieve($manager, $io);
232
233 $output = implode("\n", $messages);
234 $this->assertStringContainsString('WARNING: 1 markup warning(s), 0 key collision(s), see ', $output);
235 $this->assertStringNotContainsString('WARNING: Markup not allowed', $output);
236 }
237
239 {
240 $this->writeShippedPo('ita/', 'ita', 'de', ['one' => 'A1', 'two' => 'A2']);
241 $this->writeShippedPo('itb/', 'itb', 'de', ['one' => 'B1', 'two' => 'B2']);
242 $manager = $this->directoryManager(
243 MigratedPoFixture::directory('ita', 'ita/'),
244 MigratedPoFixture::directory('itb', 'itb/')
245 );
246
247 $first = [];
248 $this->achieve($manager, $this->informer($first));
249 $second = [];
250 $this->achieve($manager, $this->informer($second));
251
252 $grouped = array_values(array_filter($first, static fn(string $m): bool => str_contains($m, 'different values')));
253 $this->assertCount(1, $grouped, 'two keys of the same modules: one line');
254 $this->assertStringContainsString('WARNING: 2 key(s) with different values in the modules ita, itb', $grouped[0]);
255 $this->assertStringEndsWith(': one, two', $grouped[0]);
256
257 $this->assertSame([], array_filter($second, static fn(string $m): bool => str_contains($m, 'different values')));
258 $counted = array_values(array_filter($second, static fn(string $m): bool => str_contains($m, 'key collision(s)')));
259 $this->assertCount(1, $counted);
260 $this->assertMatchesRegularExpression('~^WARNING: 0 markup warning\‍(s\‍), 2 key collision\‍(s\‍), see \S+/manifest\.json\.$~', $counted[0]);
261 }
262
267 {
268 $this->writeShippedPo('broken/', 'broken', 'de', ['greeting' => 'Funktioniert']);
269 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
270 $manager = $this->directoryManager(
271 MigratedPoFixture::directory('broken', 'broken/'),
272 MigratedPoFixture::directory('itest', 'itest/')
273 );
274 $this->achieve($manager);
275 file_put_contents($this->root . '/broken/broken_de.po', "msgid \"kaputt\n");
276 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo, neu']);
277
278 $messages = [];
279 $io = $this->informer($messages);
280 $this->achieve($manager, $io);
281
282 $warning = implode("\n", $messages);
283 $this->assertStringContainsString('WARNING: Could not compile', $warning);
284 $this->assertStringContainsString('broken', $warning);
285 $this->assertSame(['greeting' => 'Funktioniert'], MigratedPoFixture::readMo($this->artifactPath('de', 'broken')));
286 $this->assertSame(['greeting' => 'Hallo, neu'], MigratedPoFixture::readMo($this->artifactPath('de', 'itest')));
287 $this->assertStringContainsString(', 1 compiled', $messages[array_key_last($messages)]);
288 }
289
292 {
293 mkdir($this->root . '/broken', 0775, true);
294 file_put_contents($this->root . '/broken/broken_de.po', "msgid \"kaputt\n");
295 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
296 $manager = $this->directoryManager(
297 MigratedPoFixture::directory('broken', 'broken/'),
298 MigratedPoFixture::directory('itest', 'itest/')
299 );
300
301 $messages = [];
302 $io = $this->informer($messages);
303 $this->achieve($manager, $io);
304
305 $warning = implode("\n", $messages);
306 $this->assertStringContainsString('WARNING: Could not compile', $warning);
307 $this->assertStringContainsString('"broken" is not served for "de"', $warning);
308 $this->assertFileDoesNotExist($this->artifactPath('de', 'broken'));
309 $this->assertSame(['greeting' => 'Hallo'], MigratedPoFixture::readMo($this->artifactPath('de', 'itest')));
310 }
311
317 {
318 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
319 $directory = MigratedPoFixture::directory('itest', 'itest/');
320 $outside = $this->root . '/outside-de';
321 mkdir($outside, 0775, true);
322 mkdir(MigratedLanguageFilePaths::shippedArtifactDirectory($this->root), 0775, true);
323 symlink($outside, MigratedLanguageFilePaths::shippedArtifactDirectory($this->root) . '/de');
324
325 $messages = [];
326 $io = $this->informer($messages);
327 $this->achieve($this->directoryManager($directory), $io);
328
329 $this->assertSame([], array_values(array_diff(scandir($outside) ?: [], ['.', '..'])));
330 }
331
333 {
334 $manager_one = $this->directoryManager(MigratedPoFixture::directory('itest', 'itest/'));
335 $manager_two = $this->directoryManager(MigratedPoFixture::directory('other', 'other/'));
336
337 $objective_one = new ShippedLanguageFilesCompiledObjective($manager_one, $this->root);
338 $objective_two = new ShippedLanguageFilesCompiledObjective($manager_two, $this->root);
339
340 $this->assertNotSame($objective_one->getHash(), $objective_two->getHash());
341 }
342
348 {
349 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
350 $directory = MigratedPoFixture::directory('itest', 'itest/');
351 $manager = $this->directoryManager($directory);
352 $this->achieve($manager);
353
354 // simulate a crash that left a truncated artifact behind - same source .po, damaged artifact
355 // (not the file this process has just read through native gettext, see the fixture)
357 $full = (string) file_get_contents($this->artifactPath('de', 'itest'));
358 file_put_contents($this->artifactPath('de', 'itest'), substr($full, 0, 10));
359
360 $messages = [];
361 $io = $this->informer($messages);
362 $this->achieve($manager, $io);
363
364 $this->assertStringContainsString(', 1 compiled', $messages[array_key_last($messages)]);
365 $this->assertSame(
366 ['greeting' => 'Hallo'],
367 MigratedPoFixture::readMo($this->artifactPath('de', 'itest'))
368 );
369 }
370
375 {
376 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
377 $directory = MigratedPoFixture::directory('itest', 'itest/');
378 $manager = $this->directoryManager($directory);
379 $this->achieve($manager);
380
382 $original = (string) file_get_contents($this->artifactPath('de', 'itest'));
383 $tampered = substr($original, 0, -1) . ($original[-1] === "\x00" ? "\x01" : "\x00");
384 file_put_contents($this->artifactPath('de', 'itest'), $tampered);
385 $this->assertSame(strlen($original), strlen($tampered), 'precondition: same size, different content');
386
387 $messages = [];
388 $io = $this->informer($messages);
389 $this->achieve($manager, $io);
390
391 $this->assertStringContainsString(', 1 compiled', $messages[array_key_last($messages)]);
392 }
393
397 public function testABrokenManifestCausesARebuild(): void
398 {
399 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
400 $directory = MigratedPoFixture::directory('itest', 'itest/');
401 $manager = $this->directoryManager($directory);
402 $this->achieve($manager);
403
404 file_put_contents(dirname($this->artifactPath('de', 'itest'), 3) . '/manifest.json', '{not valid json');
405
406 $messages = [];
407 $io = $this->informer($messages);
408 $this->achieve($manager, $io);
409
410 $this->assertStringContainsString(', 1 compiled', $messages[array_key_last($messages)]);
411 }
412
417 {
418 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
419 $directory = MigratedPoFixture::directory('itest', 'itest/');
420 mkdir(MigratedLanguageFilePaths::shippedArtifactDirectory($this->root), 0775, true);
421 file_put_contents(MigratedLanguageFilePaths::shippedArtifactDirectory($this->root) . '/index.json', '{not valid json');
422
423 $messages = [];
424 $io = $this->informer($messages);
425 $this->achieve($this->directoryManager($directory), $io);
426
427 $this->assertStringContainsString(', 1 compiled', $messages[array_key_last($messages)]);
428 $this->assertFileExists($this->artifactPath('de', 'itest'));
429 $this->assertFileDoesNotExist(MigratedLanguageFilePaths::shippedArtifactDirectory($this->root) . '/index.json');
430 }
431
436 {
437 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
438 $directory = MigratedPoFixture::directory('itest', 'itest/');
439 $manager = $this->directoryManager($directory);
440 $this->achieve($manager);
441 $artifact_inode_before = fileinode($this->artifactPath('de', 'itest'));
442
443 // achieved by moving the ARTIFACT into the past, never the .po into the future (clock skew)
444 touch($this->artifactPath('de', 'itest'), time() - 1000);
445
446 $messages = [];
447 $io = $this->informer($messages);
448 $this->achieve($manager, $io);
449
450 $this->assertStringContainsString('unchanged, 0 compiled', $messages[array_key_last($messages)]);
451 clearstatcache(true, $this->artifactPath('de', 'itest'));
452 $this->assertSame($artifact_inode_before, fileinode($this->artifactPath('de', 'itest')), 'the artifact is not rewritten (same inode)');
453 }
454
459 {
460 if (posix_getuid() === 0) {
461 $this->markTestSkipped('Cannot make a file unreadable while running as root; skipping.');
462 }
463 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
464 $this->writeShippedPo('unreadable/', 'unreadable', 'de', ['greeting' => 'Lesbar']);
465 $manager = $this->directoryManager(
466 MigratedPoFixture::directory('itest', 'itest/'),
467 MigratedPoFixture::directory('unreadable', 'unreadable/')
468 );
469 $this->achieve($manager);
470 chmod($this->root . '/unreadable/unreadable_de.po', 0000);
471
472 try {
473 $messages = [];
474 $io = $this->informer($messages);
475 $this->achieve($manager, $io);
476 } finally {
477 chmod($this->root . '/unreadable/unreadable_de.po', 0664);
478 }
479
480 $warning = implode("\n", $messages);
481 $this->assertStringContainsString('WARNING: Could not compile', $warning);
482 $this->assertStringContainsString('unreadable', $warning);
483 $this->assertSame(['greeting' => 'Lesbar'], MigratedPoFixture::readMo($this->artifactPath('de', 'unreadable')));
484 $this->assertFileExists($this->artifactPath('de', 'itest'));
485 }
486
492 {
493 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
494 $directory = MigratedPoFixture::directory('itest', 'itest/');
495 $target_directory = MigratedLanguageFilePaths::shippedArtifactDirectory($this->root);
496 mkdir($target_directory . '/de', 0775, true);
497 file_put_contents($target_directory . '/.index.json.ab12cd', 'leftover');
498 file_put_contents($target_directory . '/de/.itest.mo.ab12cd', 'leftover');
499
500 $this->achieve($this->directoryManager($directory));
501
502 $this->assertFileDoesNotExist($target_directory . '/.index.json.ab12cd');
503 $this->assertFileDoesNotExist($target_directory . '/de/.itest.mo.ab12cd');
504 }
505
511 {
512 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
513 $directory = MigratedPoFixture::directory('itest', 'itest/');
514 $target_directory = MigratedLanguageFilePaths::shippedArtifactDirectory($this->root);
515 mkdir($target_directory, 0775, true);
516 $outside = $this->root . '/outside-language-dir';
517 mkdir($outside, 0775, true);
518 file_put_contents($outside . '/obsolete.mo', 'not tracked by the index at all');
519 symlink($outside, $target_directory . '/xx');
520
521 $this->achieve($this->directoryManager($directory));
522
523 $this->assertFileExists($outside . '/obsolete.mo');
524 }
525
531 {
532 $catalog = new \ILIAS\Language\ComponentTranslation\Catalog\TranslationCatalog();
533 $entry = \MigratedPoFixture::entry('itest', "bad\x1bkey", '<script>x</script>Hallo');
534 $catalog->add($entry);
535 MigratedPoFixture::writePo($this->root . '/itest/itest_de.po', $catalog);
536 $directory = MigratedPoFixture::directory('itest', 'itest/');
537
538 $messages = [];
539 $io = $this->informer($messages);
540 $this->achieve($this->directoryManager($directory), $io);
541
542 $warning = implode("\n", $messages);
543 $this->assertStringNotContainsString("\x1b", $warning);
544 $this->assertStringContainsString('bad?key', $warning);
545 }
546
547 private function directoryWithPattern(string $prefix, string $path, string $pattern): \ILIAS\Language\ComponentTranslation\LanguageFileDirectory
548 {
549 return new class ($prefix, $path, $pattern) implements
550 \ILIAS\Language\ComponentTranslation\LanguageFileDirectory,
551 \ILIAS\Language\ComponentTranslation\NamesShippedLanguageFiles {
552 public function __construct(
553 private string $prefix,
554 private string $path,
555 private string $pattern
556 ) {
557 }
558
559 public function getPrefix(): string
560 {
561 return $this->prefix;
562 }
563
564 public function getPath(): string
565 {
566 return $this->path;
567 }
568
569 public function getSuffix(): string
570 {
571 return '';
572 }
573
574 public function isLocal(): bool
575 {
576 return false;
577 }
578
579 public function getShippedFileNamePattern(): string
580 {
581 return $this->pattern;
582 }
583 };
584 }
585
590 {
591 mkdir($this->root . '/itest', 0775, true);
593 $this->root . '/itest/ilias_de.po',
594 MigratedPoFixture::catalog('itest', ['greeting' => 'Hallo'])
595 );
596 $directory = $this->directoryWithPattern('itest', 'itest/', 'ilias_%s');
597
598 $this->achieve($this->directoryManager($directory));
599
600 $this->assertFileExists($this->artifactPath('de', 'itest'));
601 $this->assertSame(
602 ['greeting' => 'Hallo'],
603 MigratedPoFixture::readMo($this->artifactPath('de', 'itest'))
604 );
605 }
606
611 {
612 $invalid = $this->directoryWithPattern('badmod', 'badmod/', 'no-placeholder-at-all');
613 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
614 $valid = MigratedPoFixture::directory('itest', 'itest/');
615
616 $messages = [];
617 $io = $this->informer($messages);
618 $this->achieve($this->directoryManager($invalid, $valid), $io);
619
620 $warning = implode("\n", $messages);
621 $this->assertStringContainsString('WARNING', $warning);
622 $this->assertStringContainsString('badmod', $warning);
623 $this->assertFileExists($this->artifactPath('de', 'itest'));
624 }
625
627 {
628 // read by the message of the condition only
629 if (!defined('ILIAS_VERSION_NUMERIC')) {
630 define('ILIAS_VERSION_NUMERIC', '12.0');
631 }
633 $this->directoryManager(),
634 $this->root
635 );
636
637 $this->assertTrue($objective->isNotable());
638 $this->assertTrue($objective->isApplicable($this->createStub(Environment::class)));
639 $preconditions = $objective->getPreconditions($this->createStub(Environment::class));
640 $this->assertCount(1, $preconditions);
641 $this->assertInstanceOf(\ILIAS\Setup\Condition\PHPExtensionLoadedCondition::class, $preconditions[0]);
642 $this->assertStringContainsString('gettext', $preconditions[0]->getLabel());
643 }
644
646 {
647 $this->assertNull((new \ILIAS\Language\ComponentTranslation\TranslationGettextRepository())->getKeys('itest', 'de', null), 'reads (and remembers) that there is no build');
648 $this->writeShippedPo('itest/', 'itest', 'de', ['greeting' => 'Hallo']);
649
650 $this->achieve($this->directoryManager(MigratedPoFixture::directory('itest', 'itest/')));
651
652 $this->assertSame(['greeting'], array_keys((array) (new \ILIAS\Language\ComponentTranslation\TranslationGettextRepository())->getKeys('itest', 'de', null)));
653 }
654}
trait RestoresProcessState
Whatever a test does to the process (locale, LANGUAGE/LC_*, LOCPATH, the static state of the runtime ...
The single place that knows where the files of a module migrated to PO/MO live:
Builds what native gettext serves for the migrated modules (see TranslationGettextRepository) from ev...
Build step (php cli/setup.php build) for the migrated modules: builds what native gettext serves from...
static directory(string $prefix, string $path, bool $local=false)
static catalog(string $module, array $entries)
static readMo(string $file)
static writePo(string $file, TranslationCatalog $catalog)
static removeDirectory(string $directory)
static moveArtifactsToFreshDirectory()
Moves the builds to a directory this process has not looked at: the C library keeps what it read from...
static entry(?string $context, string $id, string $value)
static resetRuntime(?string $artifact_directory=null)
Forgets the static state of the runtime read path and points build and runtime at $artifact_directory...
ShippedLanguageFilesCompiledObjective builds every shipped .po for native gettext (see ShippedTransla...
writeShippedPo(string $relative_path, string $module, string $lang_key, array $entries)
testAnIndexJsonOfTheFormerLayoutIsRemoved()
The index.json of the former layout (even a broken one) does not abort the build; it is removed as ob...
testAnInvalidShippedFileNamePatternWarnsAndDoesNotAbortTheBuild()
An invalid shipped file name pattern is a warning that skips only that module.
testFindsAndCompilesAShippedPoNamedByTheIliasSchema()
A module using the "ilias_%s" schema has its shipped .po found and compiled like any other.
testSkipsADirectoryWithoutAPrefix()
The main lang/ directory (empty prefix) holds no .po and is skipped without error.
achieve(LanguageFileDirectoryManager $manager, ?AdminInteraction $io=null)
directoryManager(\ILIAS\Language\ComponentTranslation\LanguageFileDirectory ... $directories)
testATouchedButOtherwiseIdenticalPoKeepsTheBuild()
Identical content but a newer .po (e.g.
testDoesNotDeleteAnObsoleteArtifactReachedOnlyThroughASymlinkedLanguageDirectory()
removeObsoleteArtifacts() must not delete through a symlinked language directory pointing outside the...
directoryWithPattern(string $prefix, string $path, string $pattern)
testAChangedPoGivesANewBuildAndKeepsThePreviousOne()
A changed .po gives a new build (every .po compiled into a new directory, since a running process nev...
testAnUnreadablePoIsReportedAndServedFromTheFormerBuild()
A .po that cannot even be read is treated like a broken one.
testRemovesLeftoverTemporaryFilesFromAnAbortedPreviousBuild()
Leftover temporary files of AtomicFileWriter (".<name>.<random>") from a crashed build are cleaned up...
testATruncatedArtifactIsRebuiltEvenThoughItsShippedPoDidNotChange()
The index also records size and hash of the artifact itself: a truncated or corrupted artifact (e....
artifactPath(string $lang_key, string $module)
The catalog of $module/$lang_key in the build current.json names; a nonexistent path without one.
testABrokenPoIsReportedAndServedFromTheFormerBuild()
A broken .po is reported and served from the former build; the fallback is not counted as compiled.
testControlCharactersInABuildLogMessageAreReplaced()
The build log must not pass raw control characters (ANSI escape, bidi override in an identifier) to t...
testAnArtifactWithTheSameSizeButDifferentContentIsRebuilt()
A silently changed artifact (same size, e.g.
testRemovesAnArtifactWhoseShippedPoNoLongerExists()
A .po that no longer exists has its artifact removed on the next build.
testWarnsAboutDisallowedMarkupButStillCompilesTheCleanedArtifact()
Disallowed markup is compiled cleaned and warned about; the build does not abort.
testABrokenPoWithoutAFormerBuildIsReportedAndNotServed()
Without a former build a broken .po is only reported and its module not served for that language.
testNeverWritesOutsideTheArtifactDirectoryEvenViaASymlinkedLanguageDirectory()
Writes stay confined to the artifact directory (AtomicFileWriter::write() gets it as $confine_to_dire...
testABrokenManifestCausesARebuild()
A build whose manifest cannot be read is never trusted: it is rebuilt.
testASecondRunWithNoChangesReportsTheArtifactAsUnchanged()
A second run with nothing changed does not recompile (same hash, source, artifact present): "unchange...
if(!isset($arguments[0])) if(count( $arguments) > 3) if(! $remove_from_lang &&isset($arguments[2]) &&!is_dir($arguments[2])) $module
This defines ways in which objectives may interact with admins during the setup.
An environment holds resources to be used in the setup process.
Definition: Environment.php:28
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.