ILIAS  trunk Revision v12.0_alpha-377-g3641b37b9db
RepositoryAndDataTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use PHPUnit\Framework\TestCase;
24
25class RepositoryAndDataTest extends TestCase
26{
27 protected function getRepository(
28 int $current_datestamp,
29 array $returns_on_query
31 return new class ($current_datestamp, $returns_on_query) extends DatabaseRepository {
32 public array $exposed_queries = [];
33
34 public function __construct(
35 protected int $current_datestamp,
36 protected array $returns_on_query
37 ) {
38 }
39
40 protected function query(string $query): \Generator
41 {
42 $this->exposed_queries[] = $query;
43 yield from $this->returns_on_query;
44 }
45
46 protected function manipulate(string $query): void
47 {
48 $this->exposed_queries[] = $query;
49 }
50
51 protected function quoteInteger(int $integer): string
52 {
53 return '~int:' . $integer . '~';
54 }
55
56 protected function quoteString(string $string): string
57 {
58 return '~string:' . $string . '~';
59 }
60
61 protected function quoteClob(string $string): string
62 {
63 return '~clob:' . $string . '~';
64 }
65
66 protected function getCurrentDatestamp(): int
67 {
68 return $this->current_datestamp;
69 }
70 };
71 }
72
73 protected function assertRecordMatchesArray(RecordInterface $record, array $data): void
74 {
75 $this->assertRecordInfosMatchesArray($record->infos(), $data);
76 if ($data['metadata'] !== '') {
77 $this->assertXmlStringEqualsXmlString(
78 $data['metadata'],
79 $record->metadata()?->saveXML() ?? ''
80 );
81 } else {
82 $this->assertNull($record->metadata());
83 }
84 }
85
86 protected function assertRecordInfosMatchesArray(RecordInfosInterface $infos, array $data): void
87 {
88 $this->assertSame((int) $data['obj_id'], $infos->objID());
89 $this->assertSame($data['identifier'], $infos->identfifier());
90 $this->assertSame((int) $data['datestamp'], $infos->datestamp()->getTimestamp());
91 }
92
93 public function testGetRecords(): void
94 {
95 $record_1 = [
96 'obj_id' => '32',
97 'identifier' => 'id32',
98 'datestamp' => '123456',
99 'deleted' => '0',
100 'metadata' => '<content>something</content>'
101 ];
102 $record_2 = [
103 'obj_id' => '456',
104 'identifier' => 'id456',
105 'datestamp' => '9345653',
106 'deleted' => '0',
107 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
108 ];
109 $record_3 = [
110 'obj_id' => '1',
111 'identifier' => 'id1',
112 'datestamp' => '65656565',
113 'deleted' => '0',
114 'metadata' => '<content>something else</content>'
115 ];
116 $record_4 = [
117 'obj_id' => '17',
118 'identifier' => 'id17',
119 'datestamp' => '283462843',
120 'deleted' => '1',
121 'metadata' => ''
122 ];
123 $repo = $this->getRepository(0, [$record_1, $record_2, $record_3, $record_4]);
124
125 $records = iterator_to_array($repo->getRecords());
126
127 $this->assertSame(
128 ['SELECT * FROM il_meta_oer_exposed ORDER BY obj_id'],
129 $repo->exposed_queries
130 );
131 $this->assertCount(4, $records);
132 $this->assertRecordMatchesArray($records[0], $record_1);
133 $this->assertRecordMatchesArray($records[1], $record_2);
134 $this->assertRecordMatchesArray($records[2], $record_3);
135 $this->assertRecordMatchesArray($records[3], $record_4);
136 }
137
138 public function testGetRecordsWithFromDate(): void
139 {
140 $record_1 = [
141 'obj_id' => '32',
142 'identifier' => 'id32',
143 'datestamp' => '123456',
144 'deleted' => '0',
145 'metadata' => '<content>something</content>'
146 ];
147 $record_2 = [
148 'obj_id' => '456',
149 'identifier' => 'id456',
150 'datestamp' => '9345653',
151 'deleted' => '0',
152 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
153 ];
154 $repo = $this->getRepository(0, [$record_1, $record_2]);
155
156 $records = iterator_to_array($repo->getRecords(
157 new \DateTimeImmutable('@1723994')
158 ));
159
160 $this->assertSame(
161 ['SELECT *' . ' FROM il_meta_oer_exposed WHERE datestamp >= ~int:1723994~ ORDER BY obj_id'],
162 $repo->exposed_queries
163 );
164 $this->assertCount(2, $records);
165 $this->assertRecordMatchesArray($records[0], $record_1);
166 $this->assertRecordMatchesArray($records[1], $record_2);
167 }
168
169 public function testGetRecordsWithUntilDate(): void
170 {
171 $record_2 = [
172 'obj_id' => '456',
173 'identifier' => 'id456',
174 'datestamp' => '9345653',
175 'deleted' => '0',
176 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
177 ];
178 $record_3 = [
179 'obj_id' => '1',
180 'identifier' => 'id1',
181 'datestamp' => '65656565',
182 'deleted' => '0',
183 'metadata' => '<content>something else</content>'
184 ];
185 $repo = $this->getRepository(0, [$record_2, $record_3]);
186
187 $records = iterator_to_array($repo->getRecords(
188 null,
189 new \DateTimeImmutable('@1763994')
190 ));
191
192 $this->assertSame(
193 ['SELECT *' . ' FROM il_meta_oer_exposed WHERE datestamp <= ~int:1763994~ ORDER BY obj_id'],
194 $repo->exposed_queries
195 );
196 $this->assertCount(2, $records);
197 $this->assertRecordMatchesArray($records[0], $record_2);
198 $this->assertRecordMatchesArray($records[1], $record_3);
199 }
200
202 {
203 $record_2 = [
204 'obj_id' => '456',
205 'identifier' => 'id456',
206 'datestamp' => '9345653',
207 'deleted' => '0',
208 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
209 ];
210 $repo = $this->getRepository(0, [$record_2]);
211
212 $records = iterator_to_array($repo->getRecords(
213 new \DateTimeImmutable('@1723994'),
214 new \DateTimeImmutable('@1763994')
215 ));
216
217 $this->assertSame(
218 ['SELECT *' . ' FROM il_meta_oer_exposed WHERE datestamp >= ~int:1723994~ AND datestamp <= ~int:1763994~ ORDER BY obj_id'],
219 $repo->exposed_queries
220 );
221 $this->assertCount(1, $records);
222 $this->assertRecordMatchesArray($records[0], $record_2);
223 }
224
225 public function testGetRecordsWithLimit(): void
226 {
227 $record = [
228 'obj_id' => '456',
229 'identifier' => 'id456',
230 'datestamp' => '9345653',
231 'deleted' => '0',
232 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
233 ];
234 $repo = $this->getRepository(0, [$record]);
235
236 $records = iterator_to_array($repo->getRecords(
237 null,
238 null,
239 5
240 ));
241
242 $this->assertSame(
243 ['SELECT *' . ' FROM il_meta_oer_exposed ORDER BY obj_id LIMIT ~int:5~'],
244 $repo->exposed_queries
245 );
246 $this->assertCount(1, $records);
247 $this->assertRecordMatchesArray($records[0], $record);
248 }
249
250 public function testGetRecordsWithOffset(): void
251 {
252 $record = [
253 'obj_id' => '456',
254 'identifier' => 'id456',
255 'datestamp' => '9345653',
256 'deleted' => '0',
257 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
258 ];
259 $repo = $this->getRepository(0, [$record]);
260
261 $records = iterator_to_array($repo->getRecords(
262 null,
263 null,
264 null,
265 5
266 ));
267
268 $this->assertSame(
269 ['SELECT *' . ' FROM il_meta_oer_exposed ORDER BY obj_id LIMIT ~int:' . PHP_INT_MAX . '~ OFFSET ~int:5~'],
270 $repo->exposed_queries
271 );
272 $this->assertCount(1, $records);
273 $this->assertRecordMatchesArray($records[0], $record);
274 }
275
276 public function testGetRecordsWithLimitAndOffset(): void
277 {
278 $record = [
279 'obj_id' => '456',
280 'identifier' => 'id456',
281 'datestamp' => '9345653',
282 'deleted' => '0',
283 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
284 ];
285 $repo = $this->getRepository(0, [$record]);
286
287 $records = iterator_to_array($repo->getRecords(
288 null,
289 null,
290 5,
291 10
292 ));
293
294 $this->assertSame(
295 ['SELECT *' . ' FROM il_meta_oer_exposed ORDER BY obj_id LIMIT ~int:5~ OFFSET ~int:10~'],
296 $repo->exposed_queries
297 );
298 $this->assertCount(1, $records);
299 $this->assertRecordMatchesArray($records[0], $record);
300 }
301
302 public function testGetRecordInfos(): void
303 {
304 $record_1 = [
305 'obj_id' => '32',
306 'identifier' => 'id32',
307 'datestamp' => '123456',
308 'deleted' => '0'
309 ];
310 $record_2 = [
311 'obj_id' => '456',
312 'identifier' => 'id456',
313 'datestamp' => '9345653',
314 'deleted' => '0'
315 ];
316 $record_3 = [
317 'obj_id' => '1',
318 'identifier' => 'id1',
319 'datestamp' => '65656565',
320 'deleted' => '0'
321 ];
322 $record_4 = [
323 'obj_id' => '17',
324 'identifier' => 'id17',
325 'datestamp' => '283462843',
326 'deleted' => '1'
327 ];
328 $repo = $this->getRepository(0, [$record_1, $record_2, $record_3, $record_4]);
329
330 $records = iterator_to_array($repo->getRecordInfos());
331
332 $this->assertSame(
333 ['SELECT obj_id, identifier, datestamp, deleted FROM il_meta_oer_exposed ORDER BY obj_id'],
334 $repo->exposed_queries
335 );
336 $this->assertCount(4, $records);
337 $this->assertRecordInfosMatchesArray($records[0], $record_1);
338 $this->assertRecordInfosMatchesArray($records[1], $record_2);
339 $this->assertRecordInfosMatchesArray($records[2], $record_3);
340 $this->assertRecordInfosMatchesArray($records[3], $record_4);
341 }
342
343 public function testGetRecordInfosWithFromDate(): void
344 {
345 $record_1 = [
346 'obj_id' => '32',
347 'identifier' => 'id32',
348 'datestamp' => '123456',
349 'deleted' => '0'
350 ];
351 $record_2 = [
352 'obj_id' => '456',
353 'identifier' => 'id456',
354 'datestamp' => '9345653',
355 'deleted' => '0'
356 ];
357 $repo = $this->getRepository(0, [$record_1, $record_2]);
358
359 $records = iterator_to_array($repo->getRecordInfos(
360 new \DateTimeImmutable('@1723994')
361 ));
362
363 $this->assertSame(
364 ['SELECT obj_id, identifier, datestamp, deleted' . ' FROM il_meta_oer_exposed WHERE datestamp >= ~int:1723994~ ORDER BY obj_id'],
365 $repo->exposed_queries
366 );
367 $this->assertCount(2, $records);
368 $this->assertRecordInfosMatchesArray($records[0], $record_1);
369 $this->assertRecordInfosMatchesArray($records[1], $record_2);
370 }
371
372 public function testGetRecordInfosWithUntilDate(): void
373 {
374 $record_2 = [
375 'obj_id' => '456',
376 'identifier' => 'id456',
377 'datestamp' => '9345653',
378 'deleted' => '0'
379 ];
380 $record_3 = [
381 'obj_id' => '1',
382 'identifier' => 'id1',
383 'datestamp' => '65656565',
384 'deleted' => '0'
385 ];
386 $repo = $this->getRepository(0, [$record_2, $record_3]);
387
388 $records = iterator_to_array($repo->getRecordInfos(
389 null,
390 new \DateTimeImmutable('@1763994')
391 ));
392
393 $this->assertSame(
394 ['SELECT obj_id, identifier, datestamp, deleted' . ' FROM il_meta_oer_exposed WHERE datestamp <= ~int:1763994~ ORDER BY obj_id'],
395 $repo->exposed_queries
396 );
397 $this->assertCount(2, $records);
398 $this->assertRecordInfosMatchesArray($records[0], $record_2);
399 $this->assertRecordInfosMatchesArray($records[1], $record_3);
400 }
401
403 {
404 $record_2 = [
405 'obj_id' => '456',
406 'identifier' => 'id456',
407 'datestamp' => '9345653',
408 'deleted' => '0'
409 ];
410 $repo = $this->getRepository(0, [$record_2]);
411
412 $records = iterator_to_array($repo->getRecordInfos(
413 new \DateTimeImmutable('@1723994'),
414 new \DateTimeImmutable('@1763994')
415 ));
416
417 $this->assertSame(
418 ['SELECT obj_id, identifier, datestamp, deleted' . ' FROM il_meta_oer_exposed WHERE datestamp >= ~int:1723994~ AND datestamp <= ~int:1763994~ ORDER BY obj_id'],
419 $repo->exposed_queries
420 );
421 $this->assertCount(1, $records);
422 $this->assertRecordInfosMatchesArray($records[0], $record_2);
423 }
424
425 public function testGetRecordInfosWithLimit(): void
426 {
427 $record = [
428 'obj_id' => '456',
429 'identifier' => 'id456',
430 'datestamp' => '9345653',
431 'deleted' => '0'
432 ];
433 $repo = $this->getRepository(0, [$record]);
434
435 $records = iterator_to_array($repo->getRecordInfos(
436 null,
437 null,
438 5
439 ));
440
441 $this->assertSame(
442 ['SELECT obj_id, identifier, datestamp, deleted' . ' FROM il_meta_oer_exposed ORDER BY obj_id LIMIT ~int:5~'],
443 $repo->exposed_queries
444 );
445 $this->assertCount(1, $records);
446 $this->assertRecordInfosMatchesArray($records[0], $record);
447 }
448
449 public function testGetRecordInfosWithOffset(): void
450 {
451 $record = [
452 'obj_id' => '456',
453 'identifier' => 'id456',
454 'datestamp' => '9345653',
455 'deleted' => '0'
456 ];
457 $repo = $this->getRepository(0, [$record]);
458
459 $records = iterator_to_array($repo->getRecordInfos(
460 null,
461 null,
462 null,
463 5
464 ));
465
466 $this->assertSame(
467 ['SELECT obj_id, identifier, datestamp, deleted' . ' FROM il_meta_oer_exposed ORDER BY obj_id LIMIT ~int:' . PHP_INT_MAX . '~ OFFSET ~int:5~'],
468 $repo->exposed_queries
469 );
470 $this->assertCount(1, $records);
471 $this->assertRecordInfosMatchesArray($records[0], $record);
472 }
473
475 {
476 $record = [
477 'obj_id' => '456',
478 'identifier' => 'id456',
479 'datestamp' => '9345653',
480 'deleted' => '0'
481 ];
482 $repo = $this->getRepository(0, [$record]);
483
484 $records = iterator_to_array($repo->getRecordInfos(
485 null,
486 null,
487 5,
488 10
489 ));
490
491 $this->assertSame(
492 ['SELECT obj_id, identifier, datestamp, deleted' . ' FROM il_meta_oer_exposed ORDER BY obj_id LIMIT ~int:5~ OFFSET ~int:10~'],
493 $repo->exposed_queries
494 );
495 $this->assertCount(1, $records);
496 $this->assertRecordInfosMatchesArray($records[0], $record);
497 }
498
499 public function testGetRecordCount(): void
500 {
501 $repo = $this->getRepository(0, [['num' => 4]]);
502
503 $count = $repo->getRecordCount();
504
505 $this->assertSame(
506 ['SELECT COUNT(*) AS num FROM il_meta_oer_exposed'],
507 $repo->exposed_queries
508 );
509 $this->assertSame(4, $count);
510 }
511
512 public function testGetRecordCountWithFromDate(): void
513 {
514 $repo = $this->getRepository(0, [['num' => 3]]);
515
516 $count = $repo->getRecordCount(
517 new \DateTimeImmutable('@1723994')
518 );
519
520 $this->assertSame(
521 ['SELECT' . ' COUNT(*) AS num FROM il_meta_oer_exposed WHERE datestamp >= ~int:1723994~'],
522 $repo->exposed_queries
523 );
524 $this->assertSame(3, $count);
525 }
526
527 public function testGetRecordCountWithUntilDate(): void
528 {
529 $repo = $this->getRepository(0, [['num' => 3]]);
530
531 $count = $repo->getRecordCount(
532 null,
533 new \DateTimeImmutable('@1763994')
534 );
535
536 $this->assertSame(
537 ['SELECT' . ' COUNT(*) AS num FROM il_meta_oer_exposed WHERE datestamp <= ~int:1763994~'],
538 $repo->exposed_queries
539 );
540 $this->assertSame(3, $count);
541 }
542
544 {
545 $repo = $this->getRepository(0, [['num' => 2]]);
546
547 $count = $repo->getRecordCount(
548 new \DateTimeImmutable('@1723994'),
549 new \DateTimeImmutable('@1763994')
550 );
551
552 $this->assertSame(
553 ['SELECT' . ' COUNT(*) AS num FROM il_meta_oer_exposed WHERE datestamp >= ~int:1723994~ AND datestamp <= ~int:1763994~'],
554 $repo->exposed_queries
555 );
556 $this->assertSame(2, $count);
557 }
558
559 public function testGetEarliestDatestamp(): void
560 {
561 $repo = $this->getRepository(0, [['earliest' => '1795857']]);
562
563 $earliest = $repo->getEarliestDatestamp();
564
565 $this->assertSame(
566 ['SELECT MIN(datestamp) AS earliest FROM il_meta_oer_exposed'],
567 $repo->exposed_queries
568 );
569 $this->assertSame(1795857, $earliest->getTimestamp());
570 }
571
572 public function testGetRecordByIdentifier(): void
573 {
574 $record = [
575 'obj_id' => '456',
576 'identifier' => 'id456',
577 'datestamp' => '9345653',
578 'deleted' => '0',
579 'metadata' => '<content><sub1>hello</sub1><sub2>world</sub2></content>'
580 ];
581 $repo = $this->getRepository(0, [$record]);
582
583 $res = $repo->getRecordByIdentifier('id456');
584
585 $this->assertSame(
586 ['SELECT * FROM il_meta_oer_exposed WHERE identifier = ~string:id456~'],
587 $repo->exposed_queries
588 );
589 $this->assertNotNull($res);
590 $this->assertRecordMatchesArray($res, $record);
591 }
592
593 public function testGetDeletedRecordByIdentifier(): void
594 {
595 $record = [
596 'obj_id' => '456',
597 'identifier' => 'id456',
598 'datestamp' => '9345653',
599 'deleted' => '1',
600 'metadata' => ''
601 ];
602 $repo = $this->getRepository(0, [$record]);
603
604 $res = $repo->getRecordByIdentifier('id456');
605
606 $this->assertSame(
607 ['SELECT * FROM il_meta_oer_exposed WHERE identifier = ~string:id456~'],
608 $repo->exposed_queries
609 );
610 $this->assertNotNull($res);
611 $this->assertRecordMatchesArray($res, $record);
612 }
613
614 public function testGetRecordByIdentifierNotFound(): void
615 {
616 $repo = $this->getRepository(0, []);
617
618 $res = $repo->getRecordByIdentifier('id456');
619
620 $this->assertSame(
621 ['SELECT * FROM il_meta_oer_exposed WHERE identifier = ~string:id456~'],
622 $repo->exposed_queries
623 );
624 $this->assertNull($res);
625 }
626
628 {
629 $repo = $this->getRepository(0, [['num' => 1]]);
630
631 $exists = $repo->doesRecordWithIdentifierExist('some id');
632
633 $this->assertSame(
634 ['SELECT COUNT(*) AS num FROM il_meta_oer_exposed WHERE identifier = ~string:some id~'],
635 $repo->exposed_queries
636 );
637 $this->assertTrue($exists);
638 }
639
641 {
642 $repo = $this->getRepository(0, [['num' => 0]]);
643
644 $exists = $repo->doesRecordWithIdentifierExist('some id');
645
646 $this->assertSame(
647 ['SELECT COUNT(*) AS num FROM il_meta_oer_exposed WHERE identifier = ~string:some id~'],
648 $repo->exposed_queries
649 );
650 $this->assertFalse($exists);
651 }
652
653 public function testDoesRecordExistForObjIDTrue(): void
654 {
655 $repo = $this->getRepository(0, [['num' => 1]]);
656
657 $exists = $repo->doesRecordExistForObjID(43);
658
659 $this->assertSame(
660 ['SELECT ' . 'COUNT(*) AS num FROM il_meta_oer_exposed WHERE obj_id = ~int:43~'],
661 $repo->exposed_queries
662 );
663 $this->assertTrue($exists);
664 }
665
666 public function testDoesRecordExistForObjIDFalse(): void
667 {
668 $repo = $this->getRepository(0, [['num' => 0]]);
669
670 $exists = $repo->doesRecordExistForObjID(43);
671
672 $this->assertSame(
673 ['SELECT ' . 'COUNT(*) AS num FROM il_meta_oer_exposed WHERE obj_id = ~int:43~'],
674 $repo->exposed_queries
675 );
676 $this->assertFalse($exists);
677 }
678
679 public function testCreateRecord(): void
680 {
681 $xml = new \DOMDocument();
682 $xml->loadXML('<content><sub1>hello</sub1><sub2>world</sub2></content>');
683
684 $repo = $this->getRepository(17646362, []);
685
686 $repo->createRecord(
687 32,
688 'id32',
689 $xml
690 );
691
692 $this->assertSame(
693 [
694 'INSERT INTO il_meta_oer_exposed (obj_id, identifier, datestamp, metadata, deleted) VALUES (' .
695 '~int:32~, ~string:id32~, ~int:17646362~, ~clob:' . $xml->saveXML() . '~, ~int:0~)'
696 ],
697 $repo->exposed_queries
698 );
699 }
700
701 public function testUpdateRecord(): void
702 {
703 $xml = new \DOMDocument();
704 $xml->loadXML('<content><sub1>hello</sub1><sub2>world</sub2></content>');
705
706 $repo = $this->getRepository(17646362, []);
707
708 $repo->updateRecord(
709 32,
710 false,
711 $xml
712 );
713
714 $this->assertSame(
715 [
716 'UPDATE il_meta_oer_exposed SET metadata = ~clob:' . $xml->saveXML() . '~, ' .
717 'deleted = ~int:0~, ' .
718 'datestamp = ~int:17646362~ WHERE obj_id = ~int:32~'
719 ],
720 $repo->exposed_queries
721 );
722 }
723
724 public function testUpdateRecordWithoutMetadata(): void
725 {
726 $repo = $this->getRepository(17646362, []);
727
728 $repo->updateRecord(
729 32,
730 true,
731 null
732 );
733
734 $this->assertSame(
735 [
736 'UPDATE il_meta_oer_exposed ' . 'SET metadata = ~clob:~, ' .
737 'deleted = ~int:1~, ' .
738 'datestamp = ~int:17646362~ WHERE obj_id = ~int:32~'
739 ],
740 $repo->exposed_queries
741 );
742 }
743
745 {
746 $current = new \DateTimeImmutable('2025-10-31');
747 $thirty_days_ago = new \DateTimeImmutable('2025-10-01');
748 $repo = $this->getRepository($current->getTimestamp(), []);
749
750 $repo->deleteRecordsMarkedAsDeletedOlderThan(new \DateInterval('P30D'));
751
752 $this->assertSame(
753 [
754 'DELETE FROM il_meta_oer_exposed WHERE deleted = 1 AND ' .
755 'datestamp <= ~int:' . $thirty_days_ago->getTimestamp() . '~'
756 ],
757 $repo->exposed_queries
758 );
759 }
760
761 public function testDeleteRecord(): void
762 {
763 $repo = $this->getRepository(0, []);
764
765 $repo->deleteRecord(32);
766
767 $this->assertSame(
768 ['DELETE ' . 'FROM il_meta_oer_exposed WHERE obj_id = ~int:32~'],
769 $repo->exposed_queries
770 );
771 }
772}
assertRecordInfosMatchesArray(RecordInfosInterface $infos, array $data)
getRepository(int $current_datestamp, array $returns_on_query)
__construct()
Constructor setup ILIAS global object @access public.
Definition: class.ilias.php:76
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
metadata()
Deleted records don't carry metadata.
$res
Definition: ltiservices.php:69