ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
class.assImagemapQuestion.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
29
44{
45 private RequestDataCollector $request; // Hate it.
46
47 // hey: prevPassSolutions - wtf is imagemap ^^
48 public $currentSolution = [];
49 // hey.
50
51 public const MODE_SINGLE_CHOICE = 0;
52 public const MODE_MULTIPLE_CHOICE = 1;
53
54 public const AVAILABLE_SHAPES = [
55 'RECT' => 'rect',
56 'CIRCLE' => 'circle',
57 'POLY' => 'poly'];
58
60 public array $answers;
61
63 public string $image_filename;
64
66 public string $imagemap_contents;
67
69 public array $coords;
70
72 protected bool $is_multiple_choice = false;
73
87 public function __construct(
88 $title = '',
89 $comment = '',
90 $author = '',
91 $owner = -1,
92 $question = '',
94 ) {
96 $this->image_filename = $image_filename;
97 $this->answers = [];
98 $this->coords = [];
99
100 $local_dic = QuestionPoolDIC::dic();
101 $this->request = $local_dic['request_data_collector'];
102 }
103
110 {
111 $this->is_multiple_choice = $is_multiple_choice;
112 }
113
119 public function getIsMultipleChoice(): bool
120 {
122 }
123
124 public function isComplete(): bool
125 {
126 if ($this->title !== ''
127 && $this->author
128 && $this->question
129 && $this->image_filename
130 && $this->answers !== []
131 && $this->getMaximumPoints() > 0
132 ) {
133 return true;
134 }
135 return false;
136 }
137
138 public function saveToDb(?int $original_id = null): void
139 {
143 parent::saveToDb($original_id);
144 }
145
146 public function saveAnswerSpecificDataToDb(): void
147 {
148 $this->db->manipulateF(
149 'DELETE FROM qpl_a_imagemap WHERE question_fi = %s',
150 [ 'integer' ],
151 [ $this->getId() ]
152 );
153
154 // Anworten wegschreiben
155 foreach ($this->answers as $key => $value) {
156 $answer_obj = $this->answers[$key];
157 $answer_obj->setOrder($key);
158 $next_id = $this->db->nextId('qpl_a_imagemap');
159 $this->db->manipulateF(
160 'INSERT INTO qpl_a_imagemap (answer_id, question_fi, answertext, points, aorder, coords, area, points_unchecked) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
161 [ 'integer', 'integer', 'text', 'float', 'integer', 'text', 'text', 'float' ],
162 [ $next_id, $this->id, $answer_obj->getAnswertext(
163 ), $answer_obj->getPoints(), $answer_obj->getOrder(
164 ), $answer_obj->getCoords(), $answer_obj->getArea(
165 ), $answer_obj->getPointsUnchecked() ]
166 );
167 }
168 }
169
170 public function saveAdditionalQuestionDataToDb(): void
171 {
172 $this->db->manipulateF(
173 'DELETE FROM ' . $this->getAdditionalTableName() . ' WHERE question_fi = %s',
174 [ 'integer' ],
175 [ $this->getId() ]
176 );
177
178 $this->db->manipulateF(
179 'INSERT INTO ' . $this->getAdditionalTableName(
180 ) . ' (question_fi, image_file, is_multiple_choice) VALUES (%s, %s, %s)',
181 [ 'integer', 'text', 'integer' ],
182 [
183 $this->getId(),
184 $this->image_filename,
185 (int) $this->is_multiple_choice
186 ]
187 );
188 }
189
192 ): \assQuestion {
193 $this->copyImagemapFiles($this->getId(), $this->getObjId(), $target->getId(), $target->getObjId());
194 return $target;
195 }
196
197 public function copyImagemapFiles(
198 int $source_question_id,
199 int $source_parent_id,
200 int $target_question_id,
201 int $target_parent_id
202 ): void {
203 $image_source_path = $this->getImagePath($source_question_id, $source_parent_id);
204 $image_target_path = $this->getImagePath($target_question_id, $target_parent_id);
205
206 if (!file_exists($image_target_path)) {
207 ilFileUtils::makeDirParents($image_target_path);
208 } else {
209 $this->removeAllImageFiles($image_target_path);
210 }
211
212 if (!file_exists($image_source_path) || !is_readable($image_source_path)) {
213 $this->log->forComponent('qpl')->alert(
214 "Could not copy imagemap question files: Source path '{$image_source_path}' does not exist or is not readable.",
215 [
216 'source_question_id' => $source_question_id,
217 'source_parent_id' => $source_parent_id,
218 'target_question_id' => $target_question_id,
219 'target_parent_id' => $target_parent_id,
220 'image_source_path' => $image_source_path,
221 'image_target_path' => $image_target_path
222 ]
223 );
224 return;
225 }
226
227 $src = opendir($image_source_path);
228 while ($src_file = readdir($src)) {
229 if ($src_file === '.' || $src_file === '..') {
230 continue;
231 }
232 copy(
233 $image_source_path . DIRECTORY_SEPARATOR . $src_file,
234 $image_target_path . DIRECTORY_SEPARATOR . $src_file
235 );
236 }
237 }
238
239 public function loadFromDb(int $question_id): void
240 {
241 $result = $this->db->queryF(
242 'SELECT qpl_questions.*, ' . $this->getAdditionalTableName() . '.* FROM qpl_questions LEFT JOIN ' . $this->getAdditionalTableName() . ' ON ' . $this->getAdditionalTableName() . '.question_fi = qpl_questions.question_id WHERE qpl_questions.question_id = %s',
243 ['integer'],
244 [$question_id]
245 );
246 if ($result->numRows() == 1) {
247 $data = $this->db->fetchAssoc($result);
248 $this->setId($question_id);
249 $this->setObjId($data['obj_fi']);
250 $this->setTitle((string) $data['title']);
251 $this->setComment((string) $data['description']);
252 $this->setOriginalId($data['original_id']);
253 $this->setNrOfTries($data['nr_of_tries']);
254 $this->setAuthor($data['author']);
255 $this->setPoints($data['points']);
256 $this->setOwner($data['owner']);
257 $this->setIsMultipleChoice($data['is_multiple_choice'] == self::MODE_MULTIPLE_CHOICE);
258 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc((string) $data['question_text'], 1));
259 $this->setImageFilename($data['image_file'] ?? '');
260
261 try {
262 $this->setLifecycle(new ilAssQuestionLifecycle($data['lifecycle']));
264 $this->setLifecycle(new ilAssQuestionLifecycle());
265 }
266
267 try {
268 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
270 }
271
272 $result = $this->db->queryF(
273 'SELECT * FROM qpl_a_imagemap WHERE question_fi = %s ORDER BY aorder ASC',
274 ['integer'],
275 [$question_id]
276 );
277 if ($result->numRows() > 0) {
278 while ($data = $this->db->fetchAssoc($result)) {
279 $image_map_question = new ASS_AnswerImagemap($data['answertext'] ?? '', $data['points'], $data['aorder']);
280 $image_map_question->setCoords($data['coords']);
281 $image_map_question->setArea($data['area']);
282 $image_map_question->setPointsUnchecked($data['points_unchecked']);
283 array_push($this->answers, $image_map_question);
284 }
285 }
286 }
287 parent::loadFromDb($question_id);
288 }
289
290 public function uploadImagemap(array $shapes): int
291 {
292 $added = 0;
293
294 if (count($shapes) > 0) {
295 foreach ($shapes as $shape) {
296 $this->addAnswer($shape->getAnswertext(), 0.0, count($this->answers), $shape->getCoords(), $shape->getArea());
297 $added++;
298 }
299 }
300
301 return $added;
302 }
303
304 public function uploadImageFile(
305 string $upload_file_name,
306 string $upload_tmp_name
307 ): void {
308 $path_parts = pathinfo($upload_file_name);
309 $suffix = $path_parts['extension'] ?? '';
310
311 if (!in_array($path_parts['extension'], ['jpg', 'jpeg', 'png', 'gif'])) {
312 $this->tpl->setOnScreenMessage('failure', 'The image could not be uploaded!');
313 return;
314 }
315
316 $image_filename = bin2hex(random_bytes(10)) . ".{$suffix}";
317
318 $imagepath = $this->getImagePath();
319 if (!file_exists($imagepath)) {
320 ilFileUtils::makeDirParents($imagepath);
321 }
322
323 if (!ilFileUtils::moveUploadedFile($upload_tmp_name, $image_filename, $imagepath . $image_filename)) {
324 $this->tpl->setOnScreenMessage('failure', 'The image could not be uploaded!');
325 return;
326 }
327
328 $this->setImageFilename($image_filename);
329 }
330
331 public function getImageFilename(): string
332 {
333 return $this->image_filename;
334 }
335
336 public function setImageFilename(
337 string $image_filename
338 ): void {
339 $this->image_filename = $image_filename;
340 }
341
342 public function get_imagemap_contents(string $href = '#'): string
343 {
344 $imagemap_contents = '<map name=\'' . $this->title . '\'> ';
345 for ($i = 0; $i < count($this->answers); $i++) {
346 $imagemap_contents .= '<area alt=\'' . $this->answers[$i]->getAnswertext() . '\' ';
347 $imagemap_contents .= 'shape=\'' . $this->answers[$i]->getArea() . '\' ';
348 $imagemap_contents .= 'coords=\'' . $this->answers[$i]->getCoords() . '\' ';
349 $imagemap_contents .= "href=\"{$href}&selimage=" . $this->answers[$i]->getOrder() . "\" /> ";
350 }
351 $imagemap_contents .= '</map>';
352 return $imagemap_contents;
353 }
354
355 public function addAnswer(
356 string $answertext = '',
357 float $points = 0.0,
358 int $order = 0,
359 string $coords = '',
360 string $area = '',
361 float $points_unchecked = 0.0
362 ): void {
363 if (array_key_exists($order, $this->answers)) {
364 // Insert answer
365 $answer = new ASS_AnswerImagemap($answertext, $points, $order, 0, -1);
366 $answer->setCoords($coords);
367 $answer->setArea($area);
368 $answer->setPointsUnchecked($points_unchecked);
369 for ($i = count($this->answers) - 1; $i >= $order; $i--) {
370 $this->answers[$i + 1] = $this->answers[$i];
371 $this->answers[$i + 1]->setOrder($i + 1);
372 }
373 $this->answers[$order] = $answer;
374 } else {
375 // Append answer
376 $answer = new ASS_AnswerImagemap($answertext, $points, count($this->answers), 0, -1);
377 $answer->setCoords($coords);
378 $answer->setArea($area);
379 $answer->setPointsUnchecked($points_unchecked);
380 array_push($this->answers, $answer);
381 }
382 }
383
384 public function getAnswerCount(): int
385 {
386 return count($this->answers);
387 }
388
389 public function getAnswer(int $index = 0): ?ASS_AnswerImagemap
390 {
391 if ($index < 0) {
392 return null;
393 }
394 if (count($this->answers) < 1) {
395 return null;
396 }
397 if ($index >= count($this->answers)) {
398 return null;
399 }
400 return $this->answers[$index];
401 }
402
403 public function &getAnswers(): array
404 {
405 return $this->answers;
406 }
407
408 public function deleteArea(int $index = 0): void
409 {
410 if ($index < 0) {
411 return;
412 }
413 if (count($this->answers) < 1) {
414 return;
415 }
416 if ($index >= count($this->answers)) {
417 return;
418 }
419 unset($this->answers[$index]);
420 $this->answers = array_values($this->answers);
421 for ($i = 0; $i < count($this->answers); $i++) {
422 if ($this->answers[$i]->getOrder() > $index) {
423 $this->answers[$i]->setOrder($i);
424 }
425 }
426 }
427
428 public function flushAnswers(): void
429 {
430 $this->answers = [];
431 }
432
433 public function getMaximumPoints(): float
434 {
435 $points = 0;
436 foreach ($this->answers as $key => $value) {
437 if ($this->is_multiple_choice) {
438 if ($value->getPoints() > $value->getPointsUnchecked()) {
439 $points += $value->getPoints();
440 } else {
441 $points += $value->getPointsUnchecked();
442 }
443 } else {
444 if ($value->getPoints() > $points) {
445 $points = $value->getPoints();
446 }
447 }
448 }
449 return $points;
450 }
451
452 public function calculateReachedPoints(
453 int $active_id,
454 ?int $pass = null,
455 bool $authorized_solution = true
456 ): float {
457 if ($pass === null) {
458 $pass = $this->getSolutionMaxPass($active_id);
459 }
460 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
461 $found_values = [];
462 while ($data = $this->db->fetchAssoc($result)) {
463 if ($data['value1'] !== '') {
464 $found_values[] = $data['value1'];
465 }
466 }
467
468 $points = $this->calculateReachedPointsForSolution($found_values);
469
470 return $points;
471 }
472
473 public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session): float
474 {
475 $solution_data = $preview_session->getParticipantsSolution();
476
477 $reached_points = $this->calculateReachedPointsForSolution(is_array($solution_data) ? array_values($solution_data) : []);
478
479 return $this->ensureNonNegativePoints($reached_points);
480 }
481
490 public function saveWorkingData(
491 int $active_id,
492 ?int $pass = null,
493 bool $authorized = true
494 ): bool {
495 if (is_null($pass)) {
496 $pass = ilObjTest::_getPass($active_id);
497 }
498
499 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
500 function () use ($active_id, $pass, $authorized) {
501 if ($authorized) {
502 // remove the dummy record of the intermediate solution
503 $this->deleteDummySolutionRecord($active_id, $pass);
504
505 // delete the authorized solution and make the intermediate solution authorized (keeping timestamps)
506 $this->removeCurrentSolution($active_id, $pass, true);
507 $this->updateCurrentSolutionsAuthorization($active_id, $pass, true, true);
508 return;
509 }
510
511 $this->forceExistingIntermediateSolution(
512 $active_id,
513 $pass,
514 $this->is_multiple_choice
515 );
516
517 if ($this->isReuseSolutionSelectionRequest()) {
518 $selection = $this->getReuseSolutionSelectionParameter();
519
520 foreach ($selection as $selectedIndex) {
521 $this->saveCurrentSolution($active_id, $pass, (int) $selectedIndex, null, $authorized);
522 }
523 return;
524 }
525
526 if ($this->isRemoveSolutionSelectionRequest()) {
527 $selection = $this->getRemoveSolutionSelectionParameter();
528 $this->deleteSolutionRecordByValues($active_id, $pass, $authorized, [
529 'value1' => (int) $selection
530 ]);
531 return;
532 }
533
534 if (!$this->isAddSolutionSelectionRequest()) {
535 return;
536 }
537 $selection = $this->getAddSolutionSelectionParameter();
538
539 if ($this->is_multiple_choice) {
540 $this->deleteSolutionRecordByValues($active_id, $pass, $authorized, [
541 'value1' => (int) $this->request->raw('selImage')
542 ]);
543 } else {
544 $this->removeCurrentSolution($active_id, $pass, $authorized);
545 }
546
547 $this->saveCurrentSolution($active_id, $pass, $this->request->raw('selImage'), null, $authorized);
548 }
549 );
550
551 return true;
552 }
553
554 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
555 {
556 $solution = $previewSession->getParticipantsSolution();
557
558 if ($this->is_multiple_choice
559 && $this->request->isset('remImage')) {
560 unset($solution[$this->request->int('remImage')]);
561 }
562
563 if ($this->request->isset('selImage')) {
564 if (!$this->is_multiple_choice) {
565 $solution = [];
566 }
567
568 $solution[$this->request->int('selImage')] = $this->request->int('selImage');
569 }
570
571 $previewSession->setParticipantsSolution($solution);
572 }
573
582 public function getQuestionType(): string
583 {
584 return 'assImagemapQuestion';
585 }
586
595 public function getAdditionalTableName(): string
596 {
597 return 'qpl_qst_imagemap';
598 }
599
608 public function getAnswerTableName(): string
609 {
610 return 'qpl_a_imagemap';
611 }
612
617 public function getRTETextWithMediaObjects(): string
618 {
619 $text = parent::getRTETextWithMediaObjects();
620 foreach ($this->answers as $index => $answer) {
621 $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), 0, $index);
622 }
623 return $text;
624 }
625
629 public function deleteImage(): void
630 {
631 $file = $this->getImagePath() . $this->getImageFilename();
632 @unlink($file);
633 $this->flushAnswers();
634 $this->image_filename = '';
635 }
636
640 public function toJSON(): string
641 {
642 $result = [];
643 $result['id'] = $this->getId();
644 $result['type'] = (string) $this->getQuestionType();
645 $result['title'] = $this->getTitleForHTMLOutput();
646 $result['question'] = $this->formatSAQuestion($this->getQuestion());
647 $result['nr_of_tries'] = $this->getNrOfTries();
648 $result['shuffle'] = $this->getShuffle();
649 $result['is_multiple'] = $this->getIsMultipleChoice();
650 $result['feedback'] = [
651 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
652 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
653 ];
654 $result['image'] = $this->getImagePathWeb() . $this->getImageFilename();
655
656 $answers = [];
657 $order = 0;
658 foreach ($this->getAnswers() as $key => $answer_obj) {
659 array_push($answers, [
660 'answertext' => (string) $answer_obj->getAnswertext(),
661 'points' => (float) $answer_obj->getPoints(),
662 'points_unchecked' => (float) $answer_obj->getPointsUnchecked(),
663 'order' => $order,
664 'coords' => $answer_obj->getCoords(),
665 'state' => $answer_obj->getState(),
666 'area' => $answer_obj->getArea(),
667 'feedback' => $this->formatSAQuestion(
668 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
669 )
670 ]);
671 $order++;
672 }
673 $result['answers'] = $answers;
674
675 $mobs = ilObjMediaObject::_getMobsOfObject('qpl:html', $this->getId());
676 $result['mobs'] = $mobs;
677
678 return json_encode($result);
679 }
680
681 protected function calculateReachedPointsForSolution(?array $found_values): float
682 {
683 if ($found_values === null) {
684 $found_values = [];
685 }
686 $points = 0;
687 if (count($found_values) > 0) {
688 foreach ($this->answers as $key => $answer) {
689 if (in_array($key, $found_values)) {
690 $points += $answer->getPoints();
691 } elseif ($this->getIsMultipleChoice()) {
692 $points += $answer->getPointsUnchecked();
693 }
694 }
695 return $points;
696 }
697 return $points;
698 }
699
700 public function getOperators(string $expression): array
701 {
702 return ilOperatorsExpressionMapping::getOperatorsByExpression($expression);
703 }
704
705 public function getExpressionTypes(): array
706 {
707 return [
708 iQuestionCondition::PercentageResultExpression,
709 iQuestionCondition::NumberOfResultExpression,
710 iQuestionCondition::EmptyAnswerExpression,
711 iQuestionCondition::ExclusiveResultExpression
712 ];
713 }
714
715 public function getUserQuestionResult(
716 int $active_id,
717 int $pass
718 ): ilUserQuestionResult {
719 $result = new ilUserQuestionResult($this, $active_id, $pass);
720
721 $maxStep = $this->lookupMaxStep($active_id, $pass);
722 if ($maxStep > 0) {
723 $data = $this->db->queryF(
724 'SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s',
725 ['integer', 'integer', 'integer', 'integer'],
726 [$active_id, $pass, $this->getId(), $maxStep]
727 );
728 } else {
729 $data = $this->db->queryF(
730 'SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step IS NULL',
731 ['integer', 'integer', 'integer'],
732 [$active_id, $pass, $this->getId()]
733 );
734 }
735
736 while ($row = $this->db->fetchAssoc($data)) {
737 $result->addKeyValue($row['value1'], $row['value1']);
738 }
739
740 $points = $this->calculateReachedPoints($active_id, $pass);
741 $max_points = $this->getMaximumPoints();
742
743 $result->setReachedPercentage(($points / $max_points) * 100);
744
745 return $result;
746 }
747
755 public function getAvailableAnswerOptions($index = null)
756 {
757 if ($index !== null) {
758 return $this->getAnswer($index);
759 } else {
760 return $this->getAnswers();
761 }
762 }
763
764 public function getTestOutputSolutions($activeId, $pass): array
765 {
766 $solution = parent::getTestOutputSolutions($activeId, $pass);
767
768 $this->currentSolution = [];
769 foreach ($solution as $record) {
770 $this->currentSolution[] = $record['value1'];
771 }
772
773 return $solution;
774 }
775 protected function getAddSolutionSelectionParameter()
776 {
777 if (!$this->isAddSolutionSelectionRequest()) {
778 return null;
779 }
780
781 return $this->request->raw('selImage');
782 }
783 protected function isAddSolutionSelectionRequest(): bool
784 {
785 if (!$this->request->isset('selImage')) {
786 return false;
787 }
788
789 if (!strlen($this->request->raw('selImage'))) {
790 return false;
791 }
792
793 return true;
794 }
795 protected function getRemoveSolutionSelectionParameter()
796 {
797 if (!$this->isRemoveSolutionSelectionRequest()) {
798 return null;
799 }
800
801 return $this->request->raw('remImage');
802 }
803 protected function isRemoveSolutionSelectionRequest(): bool
804 {
805 if (!$this->is_multiple_choice) {
806 return false;
807 }
808
809 if (!$this->request->isset('remImage')) {
810 return false;
811 }
812
813 if (!strlen($this->request->raw('remImage'))) {
814 return false;
815 }
816
817 return true;
818 }
819 protected function getReuseSolutionSelectionParameter(): ?array
820 {
821 if (!$this->isReuseSolutionSelectionRequest()) {
822 return null;
823 }
824
825 return assQuestion::explodeKeyValues($this->request->raw('reuseSelection'));
826 }
827 protected function isReuseSolutionSelectionRequest(): bool
828 {
829 if (!$this->getTestPresentationConfig()->isPreviousPassSolutionReuseAllowed()) {
830 return false;
831 }
832
833 if (!$this->request->isset('reuseSelection')) {
834 return false;
835 }
836
837 if (!strlen($this->request->raw('reuseSelection'))) {
838 return false;
839 }
840
841 if (!preg_match('/\d(,\d)*/', $this->request->raw('reuseSelection'))) {
842 return false;
843 }
844
845 return true;
846 }
847
848 public function toLog(AdditionalInformationGenerator $additional_info): array
849 {
850 $result = [
851 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
852 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
853 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
854 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
855 ->getTrueFalseTagForBool($this->getShuffle()),
856 AdditionalInformationGenerator::KEY_QUESTION_IMAGEMAP_MODE => $this->getIsMultipleChoice()
857 ? $additional_info->getTagForLangVar('tst_imap_qst_mode_mc')
858 : $additional_info->getTagForLangVar('tst_imap_qst_mode_sc'),
859 AdditionalInformationGenerator::KEY_QUESTION_IMAGEMAP_IMAGE => $this->getImagePathWeb() . $this->getImageFilename(),
860 AdditionalInformationGenerator::KEY_FEEDBACK => [
861 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion(
862 $this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)
863 ),
864 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion(
865 $this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true)
866 )
867 ]
868 ];
869
870 $answers = [];
871 $order = 0;
872 foreach ($this->getAnswers() as $key => $answer_obj) {
873 array_push($answers, [
874 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION => (string) $answer_obj->getAnswertext(),
875 AdditionalInformationGenerator::KEY_QUESTION_POINTS_CHECKED => (float) $answer_obj->getPoints(),
876 AdditionalInformationGenerator::KEY_QUESTION_POINTS_UNCHECKED => (float) $answer_obj->getPointsUnchecked(),
877 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => $order,
878 AdditionalInformationGenerator::KEY_QUESTION_IMAGEMAP_ANSWER_OPTION_COORDS => $answer_obj->getCoords(),
879 AdditionalInformationGenerator::KEY_FEEDBACK => $this->formatSAQuestion(
880 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
881 )
882 ]);
883 $order++;
884 }
885 $result[AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTIONS] = $answers;
886
887 return $result;
888 }
889
890 protected function solutionValuesToLog(
891 AdditionalInformationGenerator $additional_info,
892 array $solution_values
893 ): array {
894 $parsed_solution = [];
895 foreach ($this->getAnswers() as $id => $answer) {
896 $value = $additional_info->getTagForLangVar('unchecked');
897 foreach ($solution_values as $solution) {
898 if ($solution['value1'] == $id) {
899 $value = $additional_info->getTagForLangVar('checked');
900 break;
901 }
902 }
903 $parsed_solution["{$answer->getArea()}: {$answer->getCoords()}"] = $value;
904 }
905 return $parsed_solution;
906 }
907
908 public function solutionValuesToText(array $solution_values): array
909 {
910 $parsed_solution = [];
911 foreach ($this->getAnswers() as $id => $answer) {
912 $value = $this->lng->txt('unchecked');
913 foreach ($solution_values as $solution) {
914 if ($solution['value1'] == $id) {
915 $value = $this->lng->txt('checked');
916 break;
917 }
918 }
919 $parsed_solution[] = "{$answer->getArea()}: {$answer->getCoords()} ({$value})";
920 }
921 return $parsed_solution;
922 }
923
924 public function getCorrectSolutionForTextOutput(int $active_id, int $pass): array
925 {
926 return array_map(
927 fn(ASS_AnswerImagemap $v): string => "{$v->getArea()}: {$v->getCoords()}"
928 . "({$this->lng->txt('points')} "
929 . "{$this->lng->txt('checked')}: {$v->getPoints()}, "
930 . "{$this->lng->txt('unchecked')}: {$v->getPointsUnchecked()})",
931 $this->getAnswers()
932 );
933 }
934
935 #[\Override]
936 public function toNormalized(
937 Transformations $transformations,
938 array $context = []
939 ): array|float|bool|int|string|null
940 {
941 return [
942 ...$transformations->normalize(parent::toNormalized($transformations, $context)),
943 'image' => $transformations->normalize(new QuestionImage($this->image_filename, $this->getId())),
944 'multiple_choice' => $this->is_multiple_choice,
945 'answers' => $transformations->normalize($this->answers),
946 ];
947 }
948
949 #[\Override]
950 public function fromNormalized(
951 array $normalized,
952 Transformations $transformations
953 ): static
954 {
955 $clone = parent::fromNormalized($normalized, $transformations);
956 $clone->image_filename = $transformations->denormalize($normalized['image'], QuestionImage::class)->getFilename();
957 $clone->is_multiple_choice = $transformations->bool($normalized['multiple_choice']);
958 $clone->answers = array_map(
959 static fn(array $answer): ASS_AnswerImagemap => $transformations->denormalize($answer, new ASS_AnswerImagemap()),
960 $normalized['answers']
961 );
962
963 return $clone;
964 }
965}
Class for true/false or yes/no answers.
Provides a set of transformations for normalizing and denormalizing values.
Wrapper object for an image file within a question (e.g.
Class for image map questions.
setIsMultipleChoice($is_multiple_choice)
Set true if the Imagemapquestion is a multiplechoice Question.
__construct( $title='', $comment='', $author='', $owner=-1, $question='', $image_filename='')
assImagemapQuestion constructor
RequestDataCollector $request
copyImagemapFiles(int $source_question_id, int $source_parent_id, int $target_question_id, int $target_parent_id)
uploadImageFile(string $upload_file_name, string $upload_tmp_name)
saveToDb(?int $original_id=null)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
cloneQuestionTypeSpecificProperties(\assQuestion $target)
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
get_imagemap_contents(string $href='#')
getIsMultipleChoice()
Returns true, if the imagemap question is a multiplechoice question.
setImageFilename(string $image_filename)
saveQuestionDataToDb(?int $original_id=null)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$data
Definition: storeScorm.php:45