ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.assMultipleChoice.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
23use ILIAS\TestQuestionPool\ManipulateImagesInChoiceQuestionsTrait;
26
43{
44 use ManipulateImagesInChoiceQuestionsTrait;
45
46 public const OUTPUT_ORDER = 0;
47 public const OUTPUT_RANDOM = 1;
48
49 public array $answers = [];
50 public bool $is_singleline = true;
51 public int $feedback_setting = 0;
52 protected ?int $selection_limit = null;
53
54 public function setIsSingleline(bool $is_singleline): void
55 {
56 $this->is_singleline = $is_singleline;
57 }
58
73 public function __construct(
74 string $title = "",
75 string $comment = "",
76 string $author = "",
77 int $owner = -1,
78 string $question = "",
79 private int $output_type = self::OUTPUT_ORDER
80 ) {
82 $this->answers = [];
83 $this->shuffle = true;
84 }
85
86 public function getSelectionLimit(): ?int
87 {
89 }
90
91 public function setSelectionLimit(?int $selection_limit): void
92 {
93 $this->selection_limit = $selection_limit;
94 }
95
96 public function isComplete(): bool
97 {
98 return $this->title !== ''
99 && $this->author !== ''
100 && $this->question !== ''
101 && $this->getAnswerCount() > 0
102 && $this->getMaximumPoints() >= 0;
103 }
104
105 public function saveToDb(?int $original_id = null): void
106 {
110 parent::saveToDb($original_id);
111 }
112
113 public function loadFromDb(int $question_id): void
114 {
115 $result = $this->db->queryF(
116 "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",
117 ["integer"],
118 [$question_id]
119 );
120 if ($result->numRows() == 1) {
121 $data = $this->db->fetchAssoc($result);
122 $this->setId($question_id);
123 $this->setObjId($data["obj_fi"]);
124 $this->setTitle($data["title"] ?? '');
125 $this->setNrOfTries($data['nr_of_tries']);
126 $this->setComment($data["description"] ?? '');
127 $this->setOriginalId($data["original_id"]);
128 $this->setAuthor($data["author"]);
129 $this->setPoints($data["points"]);
130 $this->setOwner($data["owner"]);
131 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"] ?? '', 1));
132 $shuffle = (is_null($data['shuffle'])) ? true : $data['shuffle'];
133 $this->setShuffle((bool) $shuffle);
134 if ($data['thumb_size'] !== null && $data['thumb_size'] >= $this->getMinimumThumbSize()) {
135 $this->setThumbSize($data['thumb_size']);
136 }
137 $this->is_singleline = $data['allow_images'] === null || $data['allow_images'] === '0';
138 $this->lastChange = $data['tstamp'];
139 $this->setSelectionLimit((int) $data['selection_limit'] > 0 ? (int) $data['selection_limit'] : null);
140 if (isset($data['feedback_setting'])) {
141 $this->feedback_setting = $data['feedback_setting'];
142 }
143
144 try {
145 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
148 }
149
150 try {
151 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
153 }
154 }
155
156 $result = $this->db->queryF(
157 "SELECT * FROM qpl_a_mc WHERE question_fi = %s ORDER BY aorder ASC",
158 ['integer'],
159 [$question_id]
160 );
161 if ($result->numRows() > 0) {
162 while ($data = $this->db->fetchAssoc($result)) {
163 $imagefilename = $this->getImagePath() . $data["imagefile"];
164 if (!file_exists($imagefilename)) {
165 $data["imagefile"] = null;
166 }
167 $data["answertext"] = ilRTE::_replaceMediaObjectImageSrc($data["answertext"] ?? '', 1);
168
170 $data["answertext"],
171 $data["points"],
172 $data["aorder"],
173 $data["answer_id"]
174 );
175 $answer->setPointsUnchecked($data["points_unchecked"]);
176 $answer->setImage($data["imagefile"] ? $data["imagefile"] : null);
177 array_push($this->answers, $answer);
178 }
179 }
180
181 parent::loadFromDb($question_id);
182 }
183
185 \assQuestion $target
186 ): \assQuestion {
187 $this->cloneImages(
188 $this->getId(),
189 $this->getObjId(),
190 $target->getId(),
191 $target->getObjId(),
192 $this->getAnswers()
193 );
194 return $target;
195 }
196
211 public function addAnswer(
212 string $answertext = '',
213 float $points = 0.0,
214 float $points_unchecked = 0.0,
215 int $order = 0,
216 ?string $answerimage = null,
217 int $answer_id = -1
218 ): void {
219 if (array_key_exists($order, $this->answers)) {
220 // insert answer
222 $this->getHtmlQuestionContentPurifier()->purify($answertext),
223 $points,
224 $order,
225 -1,
226 0
227 );
228 $answer->setPointsUnchecked($points_unchecked);
229 $answer->setImage($answerimage);
230 $newchoices = [];
231 for ($i = 0; $i < $order; $i++) {
232 $newchoices[] = $this->answers[$i];
233 }
234 $newchoices[] = $answer;
235 for ($i = $order, $iMax = count($this->answers); $i < $iMax; $i++) {
236 $changed = $this->answers[$i];
237 $changed->setOrder($i + 1);
238 $newchoices[] = $changed;
239 }
240 $this->answers = $newchoices;
241 return;
242 } else {
244 $this->getHtmlQuestionContentPurifier()->purify($answertext),
245 $points,
246 count($this->answers),
247 $answer_id,
248 0
249 );
250 $answer->setPointsUnchecked($points_unchecked);
251 $answer->setImage($answerimage);
252 $this->answers[] = $answer;
253 }
254 }
255
262 public function getAnswerCount(): int
263 {
264 return count($this->answers);
265 }
266
275 public function getAnswer($index = 0): ?object
276 {
277 if ($index < 0) {
278 return null;
279 }
280 if (count($this->answers) < 1) {
281 return null;
282 }
283 if ($index >= count($this->answers)) {
284 return null;
285 }
286
287 return $this->answers[$index];
288 }
289
297 public function deleteAnswer($index = 0): void
298 {
299 if ($index < 0) {
300 return;
301 }
302 if (count($this->answers) < 1) {
303 return;
304 }
305 if ($index >= count($this->answers)) {
306 return;
307 }
308 $answer = $this->answers[$index];
309 if ($answer->hasImage()) {
310 $this->deleteImage($answer->getImage());
311 }
312 unset($this->answers[$index]);
313 $this->answers = array_values($this->answers);
314 for ($i = 0, $iMax = count($this->answers); $i < $iMax; $i++) {
315 if ($this->answers[$i]->getOrder() > $index) {
316 $this->answers[$i]->setOrder($i);
317 }
318 }
319 }
320
326 public function flushAnswers(): void
327 {
328 $this->answers = [];
329 }
330
336 public function getMaximumPoints(): float
337 {
338 $total_max_points = 0.0;
339 foreach ($this->getAnswers() as $answer) {
340 $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked());
341 }
342 return $total_max_points;
343 }
344
345 public function calculateReachedPoints(
346 int $active_id,
347 ?int $pass = null,
348 bool $authorized_solution = true
349 ): float {
350 $found_values = [];
351 if ($pass === null) {
352 $pass = $this->getSolutionMaxPass($active_id);
353 }
354 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
355 while ($data = $this->db->fetchAssoc($result)) {
356 if ($data['value1'] !== '') {
357 array_push($found_values, $data['value1']);
358 }
359 }
360
361 return $this->calculateReachedPointsForSolution($found_values, $active_id);
362 }
363
364 public function validateSolutionSubmit(): bool
365 {
366 $submit = $this->getSolutionSubmit();
367
368 if ($this->getSelectionLimit()) {
369 if (count($submit) > $this->getSelectionLimit()) {
370 $failureMsg = sprintf(
371 $this->lng->txt('ass_mc_sel_lim_exhausted_hint'),
372 $this->getSelectionLimit(),
373 $this->getAnswerCount()
374 );
375
376 $this->tpl->setOnScreenMessage('failure', $failureMsg, true);
377 return false;
378 }
379 }
380
381 return true;
382 }
383
384 protected function isForcedEmptySolution(array $solutionSubmit): bool
385 {
386 $tst_force_form_diff_input = $this->questionpool_request->strArray('tst_force_form_diff_input');
387 return !count($solutionSubmit) && !empty($tst_force_form_diff_input);
388 }
389
390 public function saveWorkingData(
391 int $active_id,
392 ?int $pass = null,
393 bool $authorized = true
394 ): bool {
395 $pass = $pass ?? ilObjTest::_getPass($active_id);
396
397 $answer = $this->getSolutionSubmit();
398 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
399 function () use ($answer, $active_id, $pass, $authorized) {
400 $this->removeCurrentSolution($active_id, $pass, $authorized);
401
402 foreach ($answer as $value) {
403 if ($value !== '') {
404 $this->saveCurrentSolution($active_id, $pass, $value, null, $authorized);
405 }
406 }
407
408 // fau: testNav - write a dummy entry for the evil mc questions with "None of the above" checked
409 if ($this->isForcedEmptySolution($answer)) {
410 $this->saveCurrentSolution($active_id, $pass, 'mc_none_above', null, $authorized);
411 }
412 // fau.
413 }
414 );
415
416 return true;
417 }
418
420 {
421 if (!$this->is_singleline) {
422 ilFileUtils::delDir($this->getImagePath());
423 }
424
425 // save additional data
426 $this->db->replace(
427 $this->getAdditionalTableName(),
428 [
429 'shuffle' => ['text', $this->getShuffle()],
430 'allow_images' => ['text', $this->is_singleline ? 0 : 1],
431 'thumb_size' => ['integer', $this->getThumbSize()],
432 'selection_limit' => ['integer', $this->getSelectionLimit()],
433 'feedback_setting' => ['integer', $this->getSpecificFeedbackSetting()]
434 ],
435 ['question_fi' => ['integer', $this->getId()]]
436 );
437 }
438
439 public function saveAnswerSpecificDataToDb(): void
440 {
441 // Get all feedback entries
442 $result = $this->db->queryF(
443 "SELECT * FROM qpl_fb_specific WHERE question_fi = %s",
444 ['integer'],
445 [$this->getId()]
446 );
447 $db_feedback = $this->db->fetchAll($result);
448
449 // Check if feedback exists and the regular editor is used and not the page editor
450 if (sizeof($db_feedback) >= 1 && $this->getAdditionalContentEditingMode() == 'default') {
451 // Get all existing answer data for question
452 $result = $this->db->queryF(
453 "SELECT answer_id, aorder FROM qpl_a_mc WHERE question_fi = %s",
454 ['integer'],
455 [$this->getId()]
456 );
457 $db_answers = $this->db->fetchAll($result);
458
459 // Collect old and new order entries by ids and order to calculate a diff/intersection and remove/update feedback
460 $post_answer_order_for_id = [];
461 foreach ($this->answers as $answer) {
462 // Only the first appearance of an id is used
463 if ($answer->getId() !== null && !in_array($answer->getId(), array_keys($post_answer_order_for_id))) {
464 // -1 is happening while import and also if a new multi line answer is generated
465 if ($answer->getId() == -1) {
466 continue;
467 }
468 $post_answer_order_for_id[$answer->getId()] = $answer->getOrder();
469 }
470 }
471
472 // If there is no usable ids from post, it's better to not touch the feedback
473 // This is useful since the import is also using this function or the first creation of a new question in general
474 if (sizeof($post_answer_order_for_id) >= 1) {
475 $db_answer_order_for_id = [];
476 $db_answer_id_for_order = [];
477 foreach ($db_answers as $db_answer) {
478 $db_answer_order_for_id[intval($db_answer['answer_id'])] = intval($db_answer['aorder']);
479 $db_answer_id_for_order[intval($db_answer['aorder'])] = intval($db_answer['answer_id']);
480 }
481
482 // Handle feedback
483 // the diff between the already existing answer ids from the Database and the answer ids from post
484 // feedback related to the answer ids should be deleted or in our case not recreated.
485 $db_answer_ids = array_keys($db_answer_order_for_id);
486 $post_answer_ids = array_keys($post_answer_order_for_id);
487 $diff_db_post_answer_ids = array_diff($db_answer_ids, $post_answer_ids);
488 $unused_answer_ids = array_keys($diff_db_post_answer_ids);
489
490 // Delete all feedback in the database
491 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($this->getId(), false);
492 // Recreate feedback
493 foreach ($db_feedback as $feedback_option) {
494 // skip feedback which answer is deleted
495 if (in_array(intval($feedback_option['answer']), $unused_answer_ids)) {
496 continue;
497 }
498
499 // Reorder feedback
500 $feedback_order_db = intval($feedback_option['answer']);
501 $db_answer_id = $db_answer_id_for_order[$feedback_order_db] ?? null;
502 // This cuts feedback that currently would have no corresponding answer
503 // This case can happen while copying "broken" questions
504 // Or when saving a question with less answers than feedback
505 if (is_null($db_answer_id) || $db_answer_id < 0) {
506 continue;
507 }
508 $feedback_order_post = $post_answer_order_for_id[$db_answer_id];
509 $feedback_option['answer'] = $feedback_order_post;
510
511 // Recreate remaining feedback in database
512 $next_id = $this->db->nextId('qpl_fb_specific');
513 $this->db->manipulateF(
514 "INSERT INTO qpl_fb_specific (feedback_id, question_fi, answer, tstamp, feedback, question)
515 VALUES (%s, %s, %s, %s, %s, %s)",
516 ['integer', 'integer', 'integer', 'integer', 'text', 'integer'],
517 [
518 $next_id,
519 $feedback_option['question_fi'],
520 $feedback_option['answer'],
521 time(),
522 $feedback_option['feedback'],
523 $feedback_option['question']
524 ]
525 );
526 }
527 }
528 }
529
530 // Delete all entries in qpl_a_mc for question
531 $this->db->manipulateF(
532 "DELETE FROM qpl_a_mc WHERE question_fi = %s",
533 ['integer'],
534 [$this->getId()]
535 );
536
537 // Recreate answers one by one
538 foreach ($this->answers as $key => $value) {
539 $answer_obj = $this->answers[$key];
540 $next_id = $this->db->nextId('qpl_a_mc');
541 $this->db->manipulateF(
542 "INSERT INTO qpl_a_mc (answer_id, question_fi, answertext, points, points_unchecked, aorder, imagefile, tstamp)
543 VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
544 ['integer', 'integer', 'text', 'float', 'float', 'integer', 'text', 'integer'],
545 [
546 $next_id,
547 $this->getId(),
548 ilRTE::_replaceMediaObjectImageSrc($answer_obj->getAnswertext(), 0),
549 $answer_obj->getPoints(),
550 $answer_obj->getPointsUnchecked(),
551 $answer_obj->getOrder(),
552 $answer_obj->getImage(),
553 time()
554 ]
555 );
556 }
557 }
558
564 public function getQuestionType(): string
565 {
566 return "assMultipleChoice";
567 }
568
574 public function getAdditionalTableName(): string
575 {
576 return "qpl_qst_mc";
577 }
578
584 public function getAnswerTableName(): string
585 {
586 return "qpl_a_mc";
587 }
588
596 public function setImageFile($image_filename, $image_tempfilename = ""): int
597 {
598 $result = 0;
599 if (!empty($image_tempfilename)) {
600 $image_filename = str_replace(" ", "_", $image_filename);
601 $imagepath = $this->getImagePath();
602 if (!file_exists($imagepath)) {
603 ilFileUtils::makeDirParents($imagepath);
604 }
605 if (!ilFileUtils::moveUploadedFile($image_tempfilename, $image_filename, $imagepath . $image_filename)) {
606 $result = 2;
607 } else {
608 $mimetype = ilObjMediaObject::getMimeType($imagepath . $image_filename);
609 if (!preg_match("/^image/", $mimetype)) {
610 unlink($imagepath . $image_filename);
611 $result = 1;
612 } else {
613 // create thumbnail file
614 if ($this->is_singleline && ($this->getThumbSize())) {
615 $this->generateThumbForFile(
616 $image_filename,
617 $this->getImagePath(),
618 $this->getThumbSize()
619 );
620 }
621 }
622 }
623 }
624 return $result;
625 }
626
627 public function getRTETextWithMediaObjects(): string
628 {
629 $text = parent::getRTETextWithMediaObjects();
630 foreach ($this->answers as $index => $answer) {
631 $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), 0, $index);
632 $answer_obj = $this->answers[$index];
633 $text .= $answer_obj->getAnswertext();
634 }
635 return $text;
636 }
637
641 public function &getAnswers(): array
642 {
643 return $this->answers;
644 }
645
646 public function setAnswers(array $answers): void
647 {
648 $this->answers = $answers;
649 }
650
655 {
656 foreach ($this->getAnswers() as $answer) {
657 /* @var ASS_AnswerBinaryStateImage $answer */
658 $answer->setAnswertext($migrator->migrateToLmContent($answer->getAnswertext()));
659 }
660 }
661
665 public function toJSON(): string
666 {
667 $result = [];
668 $result['id'] = $this->getId();
669 $result['type'] = (string) $this->getQuestionType();
670 $result['title'] = $this->getTitleForHTMLOutput();
671 $result['question'] = $this->formatSAQuestion($this->getQuestion());
672 $result['nr_of_tries'] = $this->getNrOfTries();
673 $result['shuffle'] = $this->getShuffle();
674 $result['selection_limit'] = (int) $this->getSelectionLimit();
675 $result['feedback'] = [
676 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
677 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
678 ];
679
680 $answers = [];
681 $has_image = false;
682 foreach ($this->getAnswers() as $key => $answer_obj) {
683 if ((string) $answer_obj->getImage()) {
684 $has_image = true;
685 }
686 array_push($answers, [
687 "answertext" => $this->formatSAQuestion($answer_obj->getAnswertext()),
688 "points_checked" => (float) $answer_obj->getPointsChecked(),
689 "points_unchecked" => (float) $answer_obj->getPointsUnchecked(),
690 "order" => (int) $answer_obj->getOrder(),
691 "image" => (string) $answer_obj->getImage(),
692 "feedback" => $this->formatSAQuestion(
693 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
694 )
695 ]);
696 }
697 $result['answers'] = $answers;
698
699 if ($has_image) {
700 $result['path'] = $this->getImagePathWeb();
701 $result['thumb'] = $this->getThumbSize();
702 }
703
704 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
705 $result['mobs'] = $mobs;
706
707 return json_encode($result);
708 }
709
710 public function removeAnswerImage($index): void
711 {
712 $answer = $this->answers[$index];
713 if (is_object($answer)) {
714 $this->deleteImage($answer->getImage());
715 $answer->setImage(null);
716 }
717 }
718
720 {
721 return $this->current_user->getPref('tst_multiline_answers') === '1' ? 1 : 0;
722 }
723
724 public function setMultilineAnswerSetting($setting = 0): void
725 {
726 $this->current_user->writePref('tst_multiline_answers', (string) $setting);
727 }
728
738 public function setSpecificFeedbackSetting(int $feedback_setting): void
739 {
740 $this->feedback_setting = $feedback_setting;
741 }
742
753 {
754 if ($this->feedback_setting) {
755 return $this->feedback_setting;
756 } else {
757 return 1;
758 }
759 }
760
762 {
763 return 'feedback_correct_sc_mc';
764 }
765
766 protected function getSolutionSubmit(): array
767 {
768 $solutionSubmit = [];
769 $post = $this->dic->http()->wrapper()->post();
770
771 foreach ($this->getAnswers() as $index => $a) {
772 if ($post->has("multiple_choice_result_$index")) {
773 $value = $post->retrieve("multiple_choice_result_$index", $this->dic->refinery()->kindlyTo()->string());
774 if (is_numeric($value)) {
775 $solutionSubmit[] = $value;
776 }
777 }
778 }
779 return $solutionSubmit;
780 }
781
783 ?array $found_values,
784 int $active_id = 0
785 ): float {
786 if ($found_values === []
787 && $active_id !== 0) {
788 return 0.0;
789 }
790
791 $found_values ??= [];
792 $points = 0.0;
793 foreach ($this->answers as $key => $answer) {
794 if (in_array($key, $found_values)) {
795 $points += $answer->getPoints();
796 continue;
797 }
798 $points += $answer->getPointsUnchecked();
799 }
800
801 return $points;
802 }
803
804 public function getOperators(string $expression): array
805 {
807 }
808
809 public function getExpressionTypes(): array
810 {
811 return [
816 ];
817 }
818
819 public function getUserQuestionResult(
820 int $active_id,
821 int $pass
823 $result = new ilUserQuestionResult($this, $active_id, $pass);
824
825 $maxStep = $this->lookupMaxStep($active_id, $pass);
826 if ($maxStep > 0) {
827 $data = $this->db->queryF(
828 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
829 ["integer", "integer", "integer","integer"],
830 [$active_id, $pass, $this->getId(), $maxStep]
831 );
832 } else {
833 $data = $this->db->queryF(
834 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
835 ["integer", "integer", "integer"],
836 [$active_id, $pass, $this->getId()]
837 );
838 }
839
840 while ($row = $this->db->fetchAssoc($data)) {
841 $result->addKeyValue($row["value1"], $row["value1"]);
842 }
843
844 $points = $this->calculateReachedPoints($active_id, $pass);
845 $max_points = $this->getMaximumPoints();
846
847 $result->setReachedPercentage(($points / $max_points) * 100);
848
849 return $result;
850 }
851
858 public function getAvailableAnswerOptions($index = null)
859 {
860 if ($index !== null) {
861 return $this->getAnswer($index);
862 } else {
863 return $this->getAnswers();
864 }
865 }
866
868 {
869 $config = parent::buildTestPresentationConfig();
870 $config->setUseUnchangedAnswerLabel($this->lng->txt('tst_mc_label_none_above'));
871 return $config;
872 }
873
874 public function isSingleline(): bool
875 {
876 return $this->is_singleline;
877 }
878
879 public function toLog(AdditionalInformationGenerator $additional_info): array
880 {
881 $result = [
882 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
883 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
884 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
885 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
886 ->getTrueFalseTagForBool($this->getShuffle()),
887 'ass_mc_sel_lim_setting' => (int) $this->getSelectionLimit(),
888 AdditionalInformationGenerator::KEY_FEEDBACK => [
889 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
890 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
891 ]
892 ];
893
894 foreach ($this->getAnswers() as $key => $answer_obj) {
895 $result[AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTIONS][$key + 1] = [
896 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION => $this->formatSAQuestion($answer_obj->getAnswertext()),
897 AdditionalInformationGenerator::KEY_QUESTION_POINTS_CHECKED => (float) $answer_obj->getPointsChecked(),
898 AdditionalInformationGenerator::KEY_QUESTION_POINTS_UNCHECKED => (float) $answer_obj->getPointsUnchecked(),
899 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => (int) $answer_obj->getOrder(),
900 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_IMAGE => (string) $answer_obj->getImage(),
901 AdditionalInformationGenerator::KEY_FEEDBACK => $this->formatSAQuestion(
902 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
903 )
904 ];
905 }
906
907 return $result;
908 }
909
910 protected function solutionValuesToLog(
911 AdditionalInformationGenerator $additional_info,
912 array $solution_values
913 ): array {
914 $solution_ids = array_map(
915 static fn(array $v): string => $v['value1'],
916 $solution_values
917 );
918 $parsed_solutions = [];
919 foreach ($this->getAnswers() as $id => $answer) {
920 $checked = false;
921 if (in_array($id, $solution_ids)) {
922 $checked = true;
923 }
924 $parsed_solutions[$answer->getAnswertext()] = $additional_info
926 }
927 return $parsed_solutions;
928 }
929
930 public function solutionValuesToText(array $solution_values): array
931 {
932 $solution_ids = array_map(
933 static fn(array $v): string => $v['value1'],
934 $solution_values
935 );
936
937 return array_map(
938 function (ASS_AnswerMultipleResponseImage $v) use ($solution_ids): string {
939 $checked = 'unchecked';
940 if (in_array($v->getId(), $solution_ids)) {
941 $checked = 'checked';
942 }
943 return "{$v->getAnswertext()} ({$this->lng->txt($checked)})";
944 },
945 $this->getAnswers()
946 );
947 }
948
949 public function getCorrectSolutionForTextOutput(int $active_id, int $pass): array
950 {
951 return array_map(
952 fn(ASS_AnswerMultipleResponseImage $v): string => $v->getAnswertext()
953 . "({$this->lng->txt('points')} "
954 . "{$this->lng->txt('checked')}: {$v->getPointsChecked()}, "
955 . "{$this->lng->txt('unchecked')}: {$v->getPointsUnchecked()})",
956 $this->getAnswers()
957 );
958 }
959}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
ASS_AnswerBinaryStateImage is a class for answers with a binary state indicator (checked/unchecked,...
getAnswertext()
Gets the answer text.
Class for multiple choice tests.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
saveToDb(?int $original_id=null)
cloneQuestionTypeSpecificProperties(\assQuestion $target)
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
addAnswer(string $answertext='', float $points=0.0, float $points_unchecked=0.0, int $order=0, ?string $answerimage=null, int $answer_id=-1)
Adds a possible answer for a multiple choice question.
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
setSelectionLimit(?int $selection_limit)
toJSON()
Returns a JSON representation of the question.
getSpecificFeedbackSetting()
Gets the current feedback settings in effect for the question.
getOperators(string $expression)
Get all available operations for a specific question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getAnswerCount()
Returns the number of answers.
getAnswerTableName()
Returns the name of the answer table in the database.
loadFromDb(int $question_id)
getCorrectSolutionForTextOutput(int $active_id, int $pass)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
isForcedEmptySolution(array $solutionSubmit)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="", private int $output_type=self::OUTPUT_ORDER)
assMultipleChoice constructor
calculateReachedPointsForSolution(?array $found_values, int $active_id=0)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
setSpecificFeedbackSetting(int $feedback_setting)
Sets the feedback settings in effect for the question.
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
flushAnswers()
Deletes all answers.
deleteAnswer($index=0)
Deletes an answer with a given index.
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
MUST convert the given solution values into an array or a string that can be stored in the log.
getQuestionType()
Returns the question type of the question.
setImageFile($image_filename, $image_tempfilename="")
Sets the image file and uploads the image to the object's image directory.
getAnswer($index=0)
Returns an answer with a given index.
getExpressionTypes()
Get all available expression types for a specific question.
& getAnswers()
Returns a reference to the answers array.
setIsSingleline(bool $is_singleline)
setOriginalId(?int $original_id)
setId(int $id=-1)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
setShuffle(?bool $shuffle=true)
setQuestion(string $question="")
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
setAuthor(string $author="")
setThumbSize(int $a_size)
setComment(string $comment="")
setObjId(int $obj_id=0)
setOwner(int $owner=-1)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
setTitle(string $title="")
saveQuestionDataToDb(?int $original_id=null)
setPoints(float $points)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
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 _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
static getMimeType(string $a_file, bool $a_external=false)
get mime type for file
static getOperatorsByExpression(string $expression)
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...
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...
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...
$post
Definition: ltitoken.php:46
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
if(!file_exists('../ilias.ini.php'))