ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
TestExporter.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use ILIAS\Data\Factory as DataFactory;
25use ILIAS\Data\UUID\Factory as UUIDFactory;
29use ILIAS\Taxonomy\DomainService as Taxonomy;
33use ILIAS\Test\Questions\Properties\Repository as QuestionsRepository;
34use ILIAS\Test\Results\Data\Repository as ResultsRepository;
44
45class TestExporter implements Exporter
46{
47 public function __construct(
48 private readonly TransformationsBuilder $builder,
49 private readonly DataFactory $data_factory,
50 private readonly \ilDBInterface $db,
51 private readonly \ilTree $tree,
52 private readonly Language $lng,
53 private readonly TestLogger $logger,
54 private readonly \ilComponentRepository $component_repository,
55 private readonly IRSS $irss,
56 private readonly ParticipantRepository $participant_repository,
57 private readonly ResultsRepository $results_repository,
58 private readonly QuestionsRepository $questions_repository,
59 private readonly GeneralQuestionPropertiesRepository $general_questions_repository,
60 private readonly Taxonomy $taxonomy
61 ) {
62 }
63
64 public function prepare(ExportContext $context): void
65 {
66 $context->logger()->info('Preparing test export (1/3)...');
67 $context->assertPhase(ExportPhase::Init);
68 $context->setPhase(ExportPhase::Prepare);
69
70 $object_id = $this->extractObjectId($context);
71 if ($object_id === null) {
72 return;
73 }
74
75 $collector = new TestCollector(
76 $this->participant_repository,
77 $this->results_repository,
78 $this->questions_repository,
79 $this->general_questions_repository,
80 $this->db,
81 $this->tree,
82 $this->lng,
83 $this->logger,
84 $this->component_repository,
85 $object_id
86 );
87 $context->setCollector($collector);
88
89 $transformations = $this->builder->forExport(
90 new CollectUserIds(),
92 new UUIDFactory(),
93 $object_id
94 ),
96 $this->irss,
97 $this->logger
98 )
99 );
100
101 $context->setTransformations($transformations);
102 $context->logger()->info('...Finished preparing test export (1/3)');
103 }
104
105 private function extractObjectId(ExportContext $context): ?ObjectId
106 {
107 $target_ids = $context->target()->getObjectIds();
108
109 if ($target_ids === []) {
110 $context->logger()->warning('No target object IDs found for test export');
111 return null;
112 }
113
114 if (count($target_ids) > 1) {
115 $context->logger()->warning(
116 'Multiple target object IDs found for test export. Only the first one will be used.'
117 );
118 }
119
120 return $this->data_factory->objId(array_shift($target_ids));
121 }
122
123 private function collector(ExportContext $context): TestCollector
124 {
125 $collector = $context->collector();
126 if (!($collector instanceof TestCollector)) {
127 throw new \LogicException('Unexpected test collector');
128 }
129
130 return $collector;
131 }
132
133 public function process(ExportContext $context): void
134 {
135 $context->logger()->info('Processing test export (2/3)...');
136 $context->assertPhase(ExportPhase::Prepare);
137 $context->setPhase(ExportPhase::Process);
138
139 $context->serializer()->group(
140 'general',
141 fn() => $this->exportObject(
142 $this->collector($context),
143 $context->transformations(),
144 $context->serializer(),
145 $context
146 )
147 );
148 $context->serializer()->group(
149 'settings',
150 fn() => $this->exportSettings(
151 $this->collector($context),
152 $context->transformations(),
153 $context->serializer(),
154 $context
155 )
156 );
157 $context->serializer()->group(
158 'questions',
159 fn() => $this->exportQuestions(
160 $this->collector($context),
161 $context->transformations(),
162 $context->serializer(),
163 $context
164 )
165 );
166 $context->serializer()->group(
167 'question_set_config',
168 fn() => $this->exportQuestionSetConfig(
169 $this->collector($context),
170 $context->transformations(),
171 $context->serializer(),
172 )
173 );
174 $context->serializer()->group(
175 'additional_working_times',
176 fn() => $this->exportAdditionalWorkingTimes(
177 $this->collector($context),
178 $context->transformations(),
179 $context->serializer(),
180 )
181 );
182 $context->serializer()->group(
183 'skill_assignments',
184 fn() => $this->exportSkillAssignments(
185 $this->collector($context),
186 $context->transformations(),
187 $context->serializer(),
188 )
189 );
190 $context->serializer()->group(
191 'skill_thresholds',
192 fn() => $this->exportSkillLevelThresholds(
193 $this->collector($context),
194 $context->transformations(),
195 $context->serializer(),
196 )
197 );
198
199 if ($context->getOption() === Types::XML_WITH_RESULTS->value) {
200 $context->logger()->info('Processing test results export ...');
201 $this->processResults($context);
202 $context->logger()->info('...Finished processing test results export');
203 }
204
205 $context->logger()->info('...Finished processing test export (2/3)');
206 }
207
208 private function processResults(ExportContext $context): void
209 {
210 $context->serializer()->group(
211 'participants',
212 fn() => $this->exportParticipants(
213 $this->collector($context),
214 $context->transformations(),
215 $context->serializer()
216 )
217 );
218 $context->serializer()->group(
219 'results',
220 fn() => $this->exportResults(
221 $this->collector($context),
222 $context->transformations(),
223 $context->serializer()
224 )
225 );
226 }
227
228 public function write(ExportContext $context): void
229 {
230 $context->logger()->info('Writing test export (3/3)...');
231 $context->assertPhase(ExportPhase::Process);
232 $context->setPhase(ExportPhase::Write);
233
234 $export_dir = $context->path()->getPathToComponentExpDirInContainer();
235 $question_image_collector = $context->transformations()
236 ->normalizationProcessor(CollectQuestionImages::class);
237 if (!($question_image_collector instanceof CollectQuestionImages)) {
238 throw new \LogicException('Unexpected question image processor');
239 }
240
241 $resource_collector = $context->transformations()
242 ->normalizationProcessor(CollectResources::class);
243 if (!($resource_collector instanceof CollectResources)) {
244 throw new \LogicException('Unexpected resource processor');
245 }
246
247 foreach ($question_image_collector->getFiles() as $file) {
248 if (!file_exists($file['from'])) {
249 $context->logger()->warning('Question image file not found: ' . $file['from']);
250 continue;
251 }
252
253 $context->writer()->writeFileByFilePath(
254 $file['from'],
255 "{$export_dir}/" . $file['to']
256 );
257 }
258
259 foreach ($resource_collector->getResources() as $id => $resource) {
260 $clean_id = str_replace(['-', '_'], '', $id);
261 $file = "{$clean_id}.{$resource->getCurrentRevision()->getInformation()->getSuffix()}";
262
263 $context->writer()->writeFilesByResourceId(
264 $id,
265 "{$export_dir}/resources/{$file}"
266 );
267 }
268
269 $this->writeMappings(
270 $this->collector($context),
271 $context->transformations(),
272 $context
273 );
274
275 $context->logger()->info('...Finished writing test export (3/3)');
276 }
277
278
279 private function exportObject(
280 TestCollector $collector,
281 Transformations $transformations,
282 Serializer $serializer,
283 ExportContext $context
284 ): void {
285 $serializer->append('object', $transformations->normalize($collector->getObject()));
286
287 $obj_id = $collector->getObjectId()->toInt();
288 $context->addDependency('components/ILIAS/ILIASObject', 'common', [$obj_id]);
289 $context->addDependency('components/ILIAS/MetaData', 'tst', ["{$obj_id}:0:tst"]);
290 $context->addDependency(
291 'components/ILIAS/Taxonomy',
292 'tax',
293 $this->taxonomy->getUsageOfObject($obj_id)
294 );
295 $context->addDependency('components/ILIAS/Tracking', 'lpsettings', [$obj_id]);
296 }
297
298 private function exportSettings(
299 TestCollector $collector,
300 Transformations $transformations,
301 Serializer $serializer,
302 ExportContext $context
303 ): void {
304 $test = $collector->getObject();
305 $main_settings = $test->getMainSettings();
306
307 $serializer->append('main', $transformations->normalize($main_settings));
308 $serializer->append('scoring', $transformations->normalize($test->getScoreSettings()));
309 $serializer->append('marks', $transformations->normalize($test->getMarkSchema()));
310
311 $intro_page_id = $main_settings->getIntroductionSettings()->getIntroductionPageId();
312 if ($intro_page_id !== null && $intro_page_id !== 0) {
313 $context->addDependency('components/ILIAS/COPage', 'pg', ["tst:{$intro_page_id}"]);
314 }
315 $concluding_page_id = $main_settings->getFinishingSettings()->getConcludingRemarksPageId();
316 if ($concluding_page_id !== null && $concluding_page_id !== 0) {
317 $context->addDependency('components/ILIAS/COPage', 'pg', ["tst:{$concluding_page_id}"]);
318 }
319 }
320
321 private function exportQuestions(
322 TestCollector $collector,
323 Transformations $transformations,
324 Serializer $serializer,
325 ExportContext $context
326 ): void {
327 $question_properties = $collector->getTestQuestionProperties();
328
329 foreach ($collector->getQuestionObjects() as $question) {
330 $normalized = [
331 ... $transformations->normalize($question),
332 'feedback' => $transformations->normalize(
333 $collector->getFeedback($question)
334 ),
335 'sequence' => $question_properties[$question->getId()]->getSequenceInformation()?->getPlaceInSequence(),
336 ];
337
338 if ($question instanceof \assFormulaQuestion) {
339 $data = $collector->getUnitsAndCategories($question->getId());
340 $normalized['formula_data'] = $transformations->normalize($data);
341 }
342
343 $serializer->append('question', $normalized);
344 $context->addDependency('components/ILIAS/COPage', 'pg', ["qpl:{$question->getId()}"]);
345 }
346 }
347
348 private function exportQuestionSetConfig(
349 TestCollector $collector,
350 Transformations $transformations,
351 Serializer $serializer,
352 ): void {
353 $serializer->append(
354 'question_set_config',
355 $transformations->normalize($collector->getQuestionSetConfig())
356 );
357 }
358
359 private function exportSkillAssignments(
360 TestCollector $collector,
361 Transformations $transformations,
362 Serializer $serializer,
363 ): void {
364 foreach ($collector->getSkillAssignments() as $assignment) {
365 $serializer->append('skill_assignment', $transformations->normalize($assignment));
366 }
367 }
368
370 TestCollector $collector,
371 Transformations $transformations,
372 Serializer $serializer,
373 ): void {
374 foreach ($collector->getSkillLevelThresholds() as $threshold) {
375 $serializer->append('skill_level_threshold', $transformations->normalize($threshold));
376 }
377 }
378
379 private function exportParticipants(
380 TestCollector $collector,
381 Transformations $transformations,
382 Serializer $serializer
383 ): void {
384 $additional_data = $collector->getAdditionalParticipantData($collector->getParticipantsIds());
385
386 foreach ($collector->getParticipants() as $participant) {
387 $normalized = $transformations->normalize($participant);
388 if ($participant->getActiveId() !== null) {
389 $normalized = array_merge($normalized, $additional_data[$participant->getActiveId()]);
390 }
391
392 $serializer->append('participant', $normalized);
393 }
394 }
395
396 private function exportResults(
397 TestCollector $collector,
398 Transformations $transformations,
399 Serializer $serializer
400 ): void {
401 foreach ($collector->getParticipantsIds() as $participant_id) {
402 $serializer->append(
403 'set',
404 $transformations->normalize(
405 $collector->getResults($participant_id)
406 )
407 );
408 }
409 }
410
412 TestCollector $collector,
413 Transformations $transformations,
414 Serializer $serializer,
415 ): void {
416 foreach ($collector->getAdditionalWorkingTimes() as $additional_working_time) {
417 $serializer->append('time', $transformations->normalize($additional_working_time));
418 }
419 }
420
421 private function writeMappings(
422 TestCollector $collector,
423 Transformations $transformations,
424 ExportContext $context
425 ): void {
426 $serializer = XmlSerializer::inMemory();
427 $serializer->createDocument('Test Export Mappings');
428 $serializer->startGroup('mappings');
429
430 $user_id_processor = $transformations->normalizationProcessor(CollectUserIds::class);
431 if (!($user_id_processor instanceof CollectUserIds)) {
432 throw new \LogicException('Unexpected user ID processor');
433 }
434 $user_ids = $user_id_processor->getIds();
435 $serializer->append('users', $collector->getUserMapping($user_ids));
436
437 $resource_processor = $transformations->normalizationProcessor(CollectResources::class);
438 if (!($resource_processor instanceof CollectResources)) {
439 throw new \LogicException('Unexpected resource processor');
440 }
441 $resources = $resource_processor->getResources();
442 $serializer->append(
443 'resources',
444 array_map($transformations->normalize(...), $resources)
445 );
446
447 $serializer->endGroup('mappings');
448
449 $context->writer()->writeFileByStream(
450 Streams::ofString($serializer->write()),
451 "{$context->path()->getPathToComponentDirInContainer()}/mappings.xml"
452 );
453 }
454}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
Builds data types.
Definition: Factory.php:36
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
addDependency(string $component, string $entity, array $ids)
Collects resources during normalization and replaces mapped IDs during denormalization.
Provides a set of transformations for normalizing and denormalizing values.
Collector to aggregate data from the test object for export.
getUserMapping(array $user_ids)
Create a mapping of user IDs to the user identifier field specified in the test object's global setti...
extractObjectId(ExportContext $context)
processResults(ExportContext $context)
exportAdditionalWorkingTimes(TestCollector $collector, Transformations $transformations, Serializer $serializer,)
prepare(ExportContext $context)
exportSkillAssignments(TestCollector $collector, Transformations $transformations, Serializer $serializer,)
exportSkillLevelThresholds(TestCollector $collector, Transformations $transformations, Serializer $serializer,)
exportQuestions(TestCollector $collector, Transformations $transformations, Serializer $serializer, ExportContext $context)
exportQuestionSetConfig(TestCollector $collector, Transformations $transformations, Serializer $serializer,)
process(ExportContext $context)
writeMappings(TestCollector $collector, Transformations $transformations, ExportContext $context)
exportObject(TestCollector $collector, Transformations $transformations, Serializer $serializer, ExportContext $context)
exportResults(TestCollector $collector, Transformations $transformations, Serializer $serializer)
exportParticipants(TestCollector $collector, Transformations $transformations, Serializer $serializer)
exportSettings(TestCollector $collector, Transformations $transformations, Serializer $serializer, ExportContext $context)
collector(ExportContext $context)
__construct(private readonly TransformationsBuilder $builder, private readonly DataFactory $data_factory, private readonly \ilDBInterface $db, private readonly \ilTree $tree, private readonly Language $lng, private readonly TestLogger $logger, private readonly \ilComponentRepository $component_repository, private readonly IRSS $irss, private readonly ParticipantRepository $participant_repository, private readonly ResultsRepository $results_repository, private readonly QuestionsRepository $questions_repository, private readonly GeneralQuestionPropertiesRepository $general_questions_repository, private readonly Taxonomy $taxonomy)
Enriches question images with UUIDs and records their source-to-target file mappings.
Class for single choice questions assFormulaQuestion is a class for single choice questions.
Tree class data representation in hierachical trees using the Nested Set Model with Gaps by Joe Celco...
foreach( $report as $row)
An Exporter is responsible for orchestrating the export process.
Definition: Exporter.php:30
Readable part of repository interface to ilComponentDataDB.
Interface ilDBInterface.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
global $lng
Definition: privfeed.php:26
$data
Definition: storeScorm.php:45