ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
InstalledLanguageDatabaseRepositoryTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
26use PHPUnit\Framework\Attributes\DataProvider;
27use PHPUnit\Framework\MockObject\Stub;
28use PHPUnit\Framework\TestCase;
29
35{
36 private function createReadDatabaseMock(): Stub&ilDBInterface
37 {
38 $db = $this->createStub(ilDBInterface::class);
39 $db->method('quote')->willReturnCallback(
40 static fn(mixed $value, string $type): string => "'" . (string) $value . "'"
41 );
42 $db->method('like')->willReturnCallback(
43 static fn(string $column, string $type, string $value = "?", bool $ci = true): string =>
44 $column . " LIKE " . "'" . $value . "'"
45 );
46
47 return $db;
48 }
49
56 private function configureDbToReturnObjectRows(Stub&ilDBInterface $db, array $rows): void
57 {
58 $objects = array_map(static fn(array $row): \stdClass => (object) $row, $rows);
59 $objects[] = null; // terminates the while ($row = ...) loop
60 $db->method('fetchObject')->willReturn(...$objects);
61 }
62
67 {
68 $statement = $this->createStub(ilDBStatement::class);
69 $rows[] = null; // terminates the while ($row = ...) loop
70 $statement->method('fetchRow')->willReturn(...$rows);
71
72 return $statement;
73 }
74
75 private function createRepository(
76 ilDBInterface $db,
77 string $absolute_path = '/does/not/matter/for/db-only/tests'
80 $db,
82 new FixedInstallationRoot($absolute_path)
83 );
84 }
85
87 {
88 $db = $this->createReadDatabaseMock();
89 $db->method('query')->willReturn($this->createStub(ilDBStatement::class));
90 $this->configureDbToReturnObjectRows($db, [
91 ['title' => 'de', 'description' => 'installed', 'obj_id' => 5],
92 ['title' => 'en', 'description' => 'not_installed', 'obj_id' => 8],
93 ]);
94
95 $result = $this->createRepository($db)->getAvailableLanguages();
96
97 $this->assertSame(
98 [
99 'de' => ['obj_id' => 5, 'status' => 'installed'],
100 'en' => ['obj_id' => 8, 'status' => 'not_installed'],
101 ],
102 $result
103 );
104 }
105
107 {
108 $db = $this->createReadDatabaseMock();
109 $db->method('query')->willReturn($this->createStub(ilDBStatement::class));
110 $this->configureDbToReturnObjectRows($db, []);
111
112 $this->assertSame([], $this->createRepository($db)->getAvailableLanguages());
113 }
114
116 {
117 $db = $this->createReadDatabaseMock();
118 $captured_query = null;
119 $db->method('query')->willReturnCallback(
120 function (string $query) use (&$captured_query): ilDBStatement {
121 $captured_query = $query;
122 return $this->createStub(ilDBStatement::class);
123 }
124 );
125 $this->configureDbToReturnObjectRows($db, [
126 ['title' => 'de'],
127 ['title' => 'en'],
128 ]);
129
130 $result = $this->createRepository($db)->getInstalledLanguages();
131
132 $this->assertSame(['de', 'en'], $result);
133 $this->assertStringContainsString("type = 'lng'", $captured_query);
134 $this->assertStringContainsString("description LIKE 'installed%'", $captured_query);
135 }
136
138 {
139 $db = $this->createReadDatabaseMock();
140 $captured_query = null;
141 $db->method('query')->willReturnCallback(
142 function (string $query) use (&$captured_query): ilDBStatement {
143 $captured_query = $query;
144 return $this->createStub(ilDBStatement::class);
145 }
146 );
147 $this->configureDbToReturnObjectRows($db, [
148 ['title' => 'de'],
149 ]);
150
151 $result = $this->createRepository($db)->getInstalledLocalLanguages();
152
153 $this->assertSame(['de'], $result);
154 $this->assertStringContainsString("description = 'installed_local'", $captured_query);
155 }
156
158 {
159 $db = $this->createReadDatabaseMock();
160 $captured_query = null;
161 $db->method('query')->willReturnCallback(
162 function (string $query) use (&$captured_query): ilDBStatement {
163 $captured_query = $query;
164 return $this->createStatementReturningAssocRows([]);
165 }
166 );
167
168 $this->createRepository($db)->getLocalChanges('de', '', '');
169
170 $this->assertStringContainsString("'1980-01-01 00:00:00'", $captured_query);
171 $this->assertStringContainsString("'2200-01-01 00:00:00'", $captured_query);
172 }
173
175 {
176 $db = $this->createReadDatabaseMock();
177 $captured_query = null;
178 $db->method('query')->willReturnCallback(
179 function (string $query) use (&$captured_query): ilDBStatement {
180 $captured_query = $query;
181 return $this->createStatementReturningAssocRows([]);
182 }
183 );
184
185 $this->createRepository($db)->getLocalChanges('de', '2020-01-01 00:00:00', '2021-01-01 00:00:00');
186
187 $this->assertStringContainsString("'2020-01-01 00:00:00'", $captured_query);
188 $this->assertStringContainsString("'2021-01-01 00:00:00'", $captured_query);
189 $this->assertStringNotContainsString('1980-01-01', $captured_query);
190 $this->assertStringNotContainsString('2200-01-01', $captured_query);
191 }
192
194 {
195 $db = $this->createReadDatabaseMock();
196 $db->method('query')->willReturn($this->createStatementReturningAssocRows([
197 ['module' => 'common', 'identifier' => 'foo', 'value' => 'Foo Value'],
198 ['module' => 'common', 'identifier' => 'bar', 'value' => 'Bar Value'],
199 ['module' => 'other', 'identifier' => 'baz', 'value' => 'Baz Value'],
200 ]));
201
202 $result = $this->createRepository($db)->getLocalChanges('de');
203
204 $this->assertSame(
205 [
206 'common' => ['foo' => 'Foo Value', 'bar' => 'Bar Value'],
207 'other' => ['baz' => 'Baz Value'],
208 ],
209 $result
210 );
211 }
212
214 {
215 $db = $this->createReadDatabaseMock();
216 $db->method('query')->willReturn($this->createStatementReturningAssocRows([]));
217
218 $this->assertSame([], $this->createRepository($db)->getLocalChanges('de'));
219 }
220
222 {
223 $db = $this->createReadDatabaseMock();
224 $db->method('query')->willReturn($this->createStatementReturningAssocRows([
225 ['module' => 'common', 'identifier' => 'foo', 'value' => 'Foo Value'],
226 ['module' => 'common', 'identifier' => 'bar', 'value' => 'Bar Value'],
227 ['module' => 'other', 'identifier' => 'baz', 'value' => 'Baz Value'],
228 ]));
229
230 $result = $this->createRepository($db)->getLanguageEntries('de');
231
232 $this->assertSame(
233 [
234 'common' => ['foo' => 'Foo Value', 'bar' => 'Bar Value'],
235 'other' => ['baz' => 'Baz Value'],
236 ],
237 $result
238 );
239 }
240
242 {
243 $db = $this->createReadDatabaseMock();
244 $db->method('query')->willReturn($this->createStatementReturningAssocRows([]));
245
246 $this->assertSame([], $this->createRepository($db)->getLanguageEntries('de'));
247 }
248
254 {
255 $db = $this->createReadDatabaseMock();
256 $captured_query = null;
257 $db->method('query')->willReturnCallback(
258 function (string $query) use (&$captured_query): ilDBStatement {
259 $captured_query = $query;
260 return $this->createStatementReturningAssocRows([]);
261 }
262 );
263
264 $this->createRepository($db)->getLanguageEntries('de');
265
266 $this->assertStringContainsString("lang_key = 'de'", $captured_query);
267 $this->assertStringNotContainsString('local_change', $captured_query);
268 }
269
271 {
272 $root = $this->createTempInstallationRoot();
273 file_put_contents($root . '/lang/customizing/ilias_de.lang.local', 'irrelevant content');
274 file_put_contents($root . '/lang/customizing/ilias_en.lang.local', 'irrelevant content');
275 // wrong naming scheme (missing .local suffix, a typo): ignored
276 file_put_contents($root . '/lang/customizing/ilias_fr.lang', 'irrelevant content');
277 file_put_contents($root . '/lang/customizing/README.md', 'irrelevant content');
278
279 try {
280 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
281
282 $result = $repository->getLocalLanguages();
283
284 sort($result);
285 $this->assertSame(['de', 'en'], $result);
286 $this->assertSame(array_values(array_unique($result)), array_values($result));
287 } finally {
288 $this->removeDirectory($root);
289 }
290 }
291
297 {
298 $root = $this->createTempInstallationRoot();
299 file_put_contents($root . '/lang/customizing/ilias_de.lang.local', 'irrelevant content');
300 file_put_contents($root . '/lang/customizing/ilias_DE.lang.local', 'irrelevant content');
301 file_put_contents($root . '/lang/customizing/ilias_12.lang.local', 'irrelevant content');
302 file_put_contents($root . '/lang/customizing/ilias_Fr.lang.local', 'irrelevant content');
303 file_put_contents($root . '/lang/customizing/ilias_deu.lang.local', 'irrelevant content');
304
305 try {
306 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
307
308 $result = $repository->getLocalLanguages();
309
310 $this->assertSame(['de'], $result);
311 } finally {
312 $this->removeDirectory($root);
313 }
314 }
315
320 {
321 $root = $this->createTempInstallationRoot();
322 file_put_contents($root . '/lang/ilias_de.lang', 'irrelevant content');
323 file_put_contents($root . '/lang/ilias_DE.lang', 'irrelevant content');
324 file_put_contents($root . '/lang/ilias_12.lang', 'irrelevant content');
325
326 try {
327 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
328
329 $result = $repository->getInstallableLanguages();
330
331 $this->assertSame(['de'], $result);
332 } finally {
333 $this->removeDirectory($root);
334 }
335 }
336
342 {
343 $root = $this->createTempInstallationRoot();
344 file_put_contents($root . '/lang/ilias_de.lang', 'irrelevant content');
345 // distinct key "zz": a wrongly accepted trailing "\n" would show up as an extra entry, not collapse into "de"
346 file_put_contents($root . "/lang/ilias_zz.lang\n", 'irrelevant content');
347
348 try {
349 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
350
351 $result = $repository->getInstallableLanguages();
352
353 $this->assertSame(['de'], $result);
354 } finally {
355 $this->removeDirectory($root);
356 }
357 }
358
360 {
361 $root = $this->createTempInstallationRoot();
362
363 try {
364 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
365 $this->assertSame([], $repository->getLocalLanguages());
366 } finally {
367 $this->removeDirectory($root);
368 }
369 }
370
372 {
373 $root = $this->createTempInstallationRoot();
374 file_put_contents($root . '/lang/ilias_de.lang', 'irrelevant content');
375 file_put_contents($root . '/lang/ilias_en.lang', 'irrelevant content');
376 // a local file is not installable, nor is a name outside the "ilias_xx.lang" scheme
377 file_put_contents($root . '/lang/ilias_fr.lang.local', 'irrelevant content');
378 file_put_contents($root . '/lang/notes.txt', 'irrelevant content');
379
380 try {
381 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
382
383 $result = $repository->getInstallableLanguages();
384
385 sort($result);
386 $this->assertSame(['de', 'en'], $result);
387 } finally {
388 $this->removeDirectory($root);
389 }
390 }
391
393 {
394 $root = $this->createTempInstallationRoot();
395
396 try {
397 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
398 // unlike checkLanguage(), a missing local file is not acceptable here
399 $this->assertFalse($repository->checkLocalLanguageFile('de'));
400 } finally {
401 $this->removeDirectory($root);
402 }
403 }
404
406 {
407 $root = $this->createTempInstallationRoot();
408 file_put_contents(
409 $root . '/lang/customizing/ilias_de.lang.local',
410 "<!-- language file start -->\ncommon#:#test#:#Custom Value\n"
411 );
412
413 try {
414 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
415 $this->assertTrue($repository->checkLocalLanguageFile('de'));
416 } finally {
417 $this->removeDirectory($root);
418 }
419 }
420
422 {
423 $root = $this->createTempInstallationRoot();
424 file_put_contents(
425 $root . '/lang/customizing/ilias_de.lang.local',
426 "common#:#test#:#Custom Value\n"
427 );
428
429 try {
430 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
431 $this->assertFalse($repository->checkLocalLanguageFile('de'));
432 } finally {
433 $this->removeDirectory($root);
434 }
435 }
436
438 {
439 $root = $this->createTempInstallationRoot();
440 file_put_contents(
441 $root . '/lang/customizing/ilias_de.lang.local',
442 "<!-- language file start -->\nonly_one_field\n"
443 );
444
445 try {
446 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
447 $this->assertFalse($repository->checkLocalLanguageFile('de'));
448 } finally {
449 $this->removeDirectory($root);
450 }
451 }
452
458 {
459 $manager = $this->createStub(LanguageFileDirectoryManager::class);
460 $manager->method('getCustomizingDirectories')->willReturnCallback(
461 static function (): \Generator {
462 return;
463 yield; // unreachable; makes this a generator that yields nothing
464 }
465 );
466
467 $repository = new InstalledLanguageDatabaseRepository(
468 $this->createReadDatabaseMock(),
469 $manager,
470 new FixedInstallationRoot('/does/not/matter/for/this/test')
471 );
472
473 $this->assertFalse($repository->checkLocalLanguageFile('de'));
474 }
475
481 private function checkLanguageWithShippedPo(?string $po_content, bool $local_directory = false): bool
482 {
483 $root = $this->createTempInstallationRoot();
484 file_put_contents($root . '/lang/ilias_de.lang', "<!-- language file start -->\ncommon#:#yes#:#Ja\n");
485 mkdir($root . '/components/pilot/lang', 0777, true);
486 if ($po_content !== null) {
487 file_put_contents($root . '/components/pilot/lang/pilot_de.po', $po_content);
488 }
489 $pilot = MigratedPoFixture::directory('pilot', 'components/pilot/lang/', $local_directory);
490
491 try {
492 $repository = new InstalledLanguageDatabaseRepository(
493 $this->createReadDatabaseMock(),
494 $local_directory
497 new FixedInstallationRoot($root)
498 );
499 return $repository->checkLanguage('de');
500 } finally {
501 $this->removeDirectory($root);
502 }
503 }
504
506 {
507 $this->assertTrue($this->checkLanguageWithShippedPo("msgctxt \"pilot\"\nmsgid \"greeting\"\nmsgstr \"Hallo\"\n"));
508 }
509
514 #[DataProvider('unparsableShippedPo')]
515 public function testCheckLanguageRejectsAnUnparsableShippedPo(string $po_content): void
516 {
517 $this->assertFalse($this->checkLanguageWithShippedPo($po_content));
518 }
519
520 public static function unparsableShippedPo(): array
521 {
522 $message = "msgctxt \"pilot\"\nmsgid \"greeting\"\nmsgstr \"Hallo\"\n";
523
524 return [
525 'unterminated string' => ["msgctxt \"pilot\"\nmsgid \"abc\nmsgstr \"Hallo\"\n"],
526 'duplicate message' => [$message . "\n" . $message],
527 'message without msgstr' => ["msgctxt \"pilot\"\nmsgid \"greeting\"\n"],
528 'truncated inside a string' => [substr($message, 0, -4)],
529 'binary data' => ["\xde\x12\x04\x95\x00\x00\x00\x00"],
530 'header value with a line break' => ["msgid \"\"\nmsgstr \"\"\n\"X-A: a\\rb\\n\"\n\n" . $message],
531 ];
532 }
533
538 {
539 $this->assertTrue($this->checkLanguageWithShippedPo(
540 "\xEF\xBB\xBFmsgid \"\"\r\nmsgstr \"\"\r\n\"Language: de\\n\"\r\n\r\nmsgctxt \"pilot\"\r\nmsgid \"greeting\"\r\nmsgstr \"Hallo\"\r\n"
541 ));
542 }
543
545 {
546 $this->assertTrue($this->checkLanguageWithShippedPo(null));
547 }
548
550 {
551 $this->assertTrue($this->checkLanguageWithShippedPo("msgid \"abc\n", true));
552 }
553
558 {
559 $this->assertTrue($this->checkLanguageWithShippedPo(''));
560 }
561
567 {
568 $root = $this->createTempInstallationRoot();
569 file_put_contents($root . '/lang/ilias_de.lang', "<!-- language file start -->\ncommon#:#yes#:#Ja\n");
570 file_put_contents($root . '/lang/customizing/ilias_de.lang.local', "<!-- language file start -->\nonly_one_field\n");
571
572 try {
573 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
574
575 $this->assertTrue($repository->checkShippedLanguage('de'), 'the invalid customizing file is never consulted');
576 $this->assertFalse($repository->checkLanguage('de'), 'precondition: the same file makes checkLanguage() fail');
577 } finally {
578 $this->removeDirectory($root);
579 }
580 }
581
586 {
587 $root = $this->createTempInstallationRoot();
588 file_put_contents($root . '/lang/ilias_de.lang', "common#:#yes#:#Ja\n");
589
590 try {
591 $repository = $this->createRepository($this->createReadDatabaseMock(), $root);
592
593 $this->assertFalse($repository->checkShippedLanguage('de'));
594 } finally {
595 $this->removeDirectory($root);
596 }
597 }
598
604 {
605 $root = $this->createTempInstallationRoot();
606 file_put_contents($root . '/lang/ilias_de.lang', "<!-- language file start -->\ncommon#:#yes#:#Ja\n");
607 mkdir($root . '/components/pilot/lang', 0777, true);
608 file_put_contents($root . '/components/pilot/lang/pilot_de.po', "msgctxt \"pilot\"\nmsgid \"abc\nmsgstr \"Hallo\"\n");
609 $pilot = MigratedPoFixture::directory('pilot', 'components/pilot/lang/');
610
611 try {
612 $repository = new InstalledLanguageDatabaseRepository(
613 $this->createReadDatabaseMock(),
615 new FixedInstallationRoot($root)
616 );
617
618 $this->assertFalse($repository->checkShippedLanguage('de'));
619 } finally {
620 $this->removeDirectory($root);
621 }
622 }
623
624 private function createTempInstallationRoot(): string
625 {
626 $dir = sys_get_temp_dir() . '/ilias_lang_repo_test_' . bin2hex(random_bytes(8));
627 mkdir($dir . '/lang/customizing', 0777, true);
628 return $dir;
629 }
630
631 private function removeDirectory(string $dir): void
632 {
633 if (!is_dir($dir)) {
634 return;
635 }
636 $items = scandir($dir);
637 foreach ($items as $item) {
638 if ($item === '.' || $item === '..') {
639 continue;
640 }
641 $path = $dir . '/' . $item;
642 if (is_dir($path) && !is_link($path)) {
643 $this->removeDirectory($path);
644 } else {
645 unlink($path);
646 }
647 }
648 rmdir($dir);
649 }
650}
A root determined by the caller, for the classes which are not wired by the component bootstrap.
Database-backed methods run against an ilDBInterface mock returning controlled rows; filesystem-backe...
testCheckShippedLanguageIgnoresAnInvalidCustomizingFileThatMakesCheckLanguageFail()
checkShippedLanguage() is checkLanguage() without the customizing directory: an invalid "ilias_de....
testCheckLanguageRejectsAnUnparsableShippedPo(string $po_content)
Everything gettext/gettext's StrictPoLoader rejects invalidates the language, including what the old ...
testGetLanguageEntriesQueryDoesNotFilterByLocalChange()
Unlike getLocalChanges(), getLanguageEntries() returns every stored entry of the language key,...
createRepository(ilDBInterface $db, string $absolute_path='/does/not/matter/for/db-only/tests')
testGetLocalLanguagesIgnoresFilesWithAnInvalidLanguageKeyShapeInTheirName()
The discovery regex must accept exactly ParsesLanguageKeyList::LANGUAGE_KEY_FORMAT ('/^[a-z]{2}$/'): ...
testCheckShippedLanguageRejectsAnUnparsableShippedPo()
An unparsable shipped .po of a PO-maintained module is part of getDirectories() too,...
testGetInstallableLanguagesIgnoresFilesWithAnInvalidLanguageKeyShapeInTheirName()
Same for getInstallableLanguages() (lang/) instead of getLocalLanguages() (lang/customizing/).
testGetInstallableLanguagesIgnoresAFileNameWithATrailingNewline()
The regex ends in \z, not $, which in PCRE also matches before a trailing newline; file names may leg...
configureDbToReturnObjectRows(Stub &ilDBInterface $db, array $rows)
The read methods call fetchObject() on the database object, not on the statement, so the rows are stu...
testCheckShippedLanguageRejectsAnInvalidGlobalLanguageFile()
An invalid global "ilias_de.lang" is part of getDirectories(), so checkShippedLanguage() fails like c...
checkLanguageWithShippedPo(?string $po_content, bool $local_directory=false)
checkLanguage() for a migrated module: the shipped .po is the only source of its values,...
testCheckLanguageAcceptsAShippedPoWithBomAndCrLf()
A BOM and CRLF line endings are no reason to refuse the language.
testCheckLocalLanguageFileReturnsFalseWhenNoCustomizingDirectoryIsConfiguredAtAll()
getCustomizingDirectories() always yields one directory in production, so the "zero directories" bran...
testCheckLanguageAcceptsAnEmptyShippedPo()
An empty .po is a valid, empty catalog.
static directory(string $prefix, string $path, bool $local=false)
return['3gp', '7z', 'ai', 'aif', 'aifc', 'aiff', 'au', 'arw', 'avi', 'backup', 'bak', 'bas', 'bpmn', 'bpmn2', 'bmp', 'bib', 'bibtex', 'bz', 'bz2', 'c', 'c++', 'cc', 'cct', 'cdf', 'cer', 'class', 'cls', 'conf', 'cpp', 'crt', 'crs', 'crw', 'cr2', 'css', 'cst', 'csv', 'cur', 'db', 'dcr', 'des', 'dng', 'doc', 'docx', 'dot', 'dotx', 'dtd', 'dvi', 'el', 'eps', 'epub', 'f', 'f77', 'f90', 'flv', 'for', 'g3', 'gif', 'gl', 'gan', 'ggb', 'gsd', 'gsm', 'gtar', 'gz', 'gzip', 'h', 'hpp', 'htm', 'html', 'htmls', 'ibooks', 'ico', 'ics', 'ini', 'ipynb', 'java', 'jbf', 'jpeg', 'jpg', 'js', 'jsf', 'jso', 'json', 'latex', 'lang', 'less', 'log', 'lsp', 'ltx', 'm1v', 'm2a', 'm2v', 'm3u', 'm4a', 'm4v', 'markdown', 'm', 'mat', 'md', 'mdl', 'mdown', 'mid', 'min', 'midi', 'mobi', 'mod', 'mov', 'movie', 'mp2', 'mp3', 'mp4', 'mpa', 'mpeg', 'mpg', 'mph', 'mpga', 'mpp', 'mpt', 'mpv', 'mpx', 'mv', 'mw', 'mv4', 'nb', 'nbp', 'nef', 'nif', 'niff', 'obj', 'obm', 'odt', 'ods', 'odp', 'odg', 'odf', 'oga', 'ogg', 'ogv', 'old', 'p', 'pas', 'pbm', 'pcl', 'pct', 'pcx', 'pdf', 'pgm', 'pic', 'pict', 'png', 'por', 'pov', 'project', 'properties', 'ppa', 'ppm', 'pps', 'ppsx', 'ppt', 'pptx', 'ppz', 'ps', 'psd', 'pwz', 'qt', 'qtc', 'qti', 'qtif', 'r', 'ra', 'ram', 'rar', 'rast', 'rda', 'rev', 'rexx', 'ris', 'rf', 'rgb', 'rm', 'rmd', 'rmi', 'rmm', 'rmp', 'rt', 'rtf', 'rtx', 'rv', 's', 's3m', 'sav', 'sbs', 'sec', 'sdml', 'sgm', 'sgml', 'smi', 'smil', 'srt', 'sps', 'spv', 'stl', 'svg', 'swa', 'swf', 'swz', 'tar', 'tex', 'texi', 'texinfo', 'text', 'tgz', 'tif', 'tiff', 'ttf', 'txt', 'tmp', 'uvproj', 'vdf', 'vimeo', 'viv', 'vivo', 'vrml', 'vsdx', 'vtt', 'wav', 'webm', 'wmv', 'wmx', 'wmz', 'woff', 'wwd', 'xhtml', 'xif', 'xls', 'xlsx', 'xmind', 'xml', 'xsl', 'xsd', 'zip']
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface ilDBInterface.
Interface ilDBStatement.