ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.assSingleChoice.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
23use ILIAS\TestQuestionPool\ManipulateImagesInChoiceQuestionsTrait;
25
40{
41 use ManipulateImagesInChoiceQuestionsTrait;
42
43 protected const OUTPUT_ORDER = 0;
44 protected const OUTPUT_RANDOM = 1;
45
46 protected const FEEDBACK_MODE_ALL_ANSWERS = 1;
47 protected const FEEDBACK_MODE_SELECTED_ANSWERS = 2;
48 protected const FEEDBACK_MODE_CORRECT_ANSWERS = 3;
49
50 private bool $is_singleline = true;
51
55 public array $answers = [];
56 public int $output_type;
58
59 public function __construct(
60 string $title = "",
61 string $comment = "",
62 string $author = "",
63 int $owner = -1,
64 string $question = "",
65 int $output_type = self::OUTPUT_ORDER
66 ) {
68 $this->output_type = $output_type;
69 }
70
71 public function isComplete(): bool
72 {
73 if (
74 $this->title !== ''
75 && $this->author !== null && $this->author !== ''
76 && $this->question !== null && $this->question !== ''
77 && $this->answers !== []
78 && $this->getMaximumPoints() > 0
79 ) {
80 foreach ($this->answers as $answer) {
81 if ($answer->getAnswertext() === '' && $answer->getImage() === '') {
82 return false;
83 }
84 }
85 return true;
86 }
87 return false;
88 }
89
90 public function saveToDb(?int $original_id = null): void
91 {
95 parent::saveToDb($original_id);
96 }
97
98 public function loadFromDb(int $question_id): void
99 {
100 $result = $this->db->queryF(
101 "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",
102 ["integer"],
103 [$question_id]
104 );
105 if ($result->numRows() == 1) {
106 $data = $this->db->fetchAssoc($result);
107 $this->setId($question_id);
108 $this->setObjId($data["obj_fi"]);
109 $this->setTitle($data["title"] ?? '');
110 $this->setNrOfTries($data['nr_of_tries']);
111 $this->setComment($data["description"] ?? '');
112 $this->setOriginalId($data["original_id"]);
113 $this->setAuthor($data["author"]);
114 $this->setPoints($data["points"]);
115 $this->setOwner($data["owner"]);
116 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"] ?? '', 1));
117 $shuffle = (is_null($data['shuffle'])) ? true : $data['shuffle'];
118 $this->setShuffle((bool) $shuffle);
119 if ($data['thumb_size'] !== null && $data['thumb_size'] >= $this->getMinimumThumbSize()) {
120 $this->setThumbSize($data['thumb_size']);
121 }
122 $this->is_singleline = $data['allow_images'] === null || $data['allow_images'] === '0';
123 $this->lastChange = $data['tstamp'];
124 $this->feedback_setting = $data['feedback_setting'] ?? self::FEEDBACK_MODE_SELECTED_ANSWERS;
125
126 try {
130 }
131
132 try {
133 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
135 }
136 }
137
138 $result = $this->db->queryF(
139 "SELECT * FROM qpl_a_sc WHERE question_fi = %s ORDER BY aorder ASC",
140 ['integer'],
141 [$question_id]
142 );
143
144 if ($result->numRows() > 0) {
145 while ($data = $this->db->fetchAssoc($result)) {
146 $imagefilename = $this->getImagePath() . $data["imagefile"];
147 if (!file_exists($imagefilename)) {
148 $data["imagefile"] = null;
149 }
150
151 $data["answertext"] = ilRTE::_replaceMediaObjectImageSrc($data["answertext"] ?? '', 1);
152 $image = new ASS_AnswerBinaryStateImage(
153 $data["answertext"],
154 $data["points"],
155 $data["aorder"],
156 true,
157 $data["imagefile"] ? $data["imagefile"] : null,
158 $data["answer_id"]
159 );
160 $this->answers[] = $image;
161 }
162 }
163
164 parent::loadFromDb($question_id);
165 }
166
168 \assQuestion $target
169 ): \assQuestion {
170 $this->cloneImages(
171 $this->getId(),
172 $this->getObjId(),
173 $target->getId(),
174 $target->getObjId(),
175 $this->getAnswers()
176 );
177 return $target;
178 }
179
180 public function addAnswer(
181 string $answertext = '',
182 float $points = 0.0,
183 int $order = 0,
184 ?string $answerimage = null,
185 int $answer_id = -1
186 ): void {
187 if (array_key_exists($order, $this->answers)) {
188 // insert answer
189 $answer = new ASS_AnswerBinaryStateImage(
190 $this->getHtmlQuestionContentPurifier()->purify($answertext),
191 $points,
192 $order,
193 true,
194 $answerimage,
195 $answer_id
196 );
197 $newchoices = [];
198 for ($i = 0; $i < $order; $i++) {
199 $newchoices[] = $this->answers[$i];
200 }
201 $newchoices[] = $answer;
202 for ($i = $order, $iMax = count($this->answers); $i < $iMax; $i++) {
203 $changed = $this->answers[$i];
204 $changed->setOrder($i + 1);
205 $newchoices[] = $changed;
206 }
207 $this->answers = $newchoices;
208 return;
209 }
210
211 $answer = new ASS_AnswerBinaryStateImage(
212 $this->getHtmlQuestionContentPurifier()->purify($answertext),
213 $points,
214 count($this->answers),
215 true,
216 $answerimage,
217 $answer_id
218 );
219 $this->answers[] = $answer;
220 }
221
229 public function getAnswerCount(): int
230 {
231 return count($this->answers);
232 }
233
243 public function getAnswer(int $index = 0): ?ASS_AnswerBinaryStateImage
244 {
245 if ($index < 0) {
246 return null;
247 }
248 if (count($this->answers) < 1) {
249 return null;
250 }
251 if ($index >= count($this->answers)) {
252 return null;
253 }
254
255 return $this->answers[$index];
256 }
257
266 public function deleteAnswer(int $index = 0): void
267 {
268 if ($index < 0) {
269 return;
270 }
271 if (count($this->answers) < 1) {
272 return;
273 }
274 if ($index >= count($this->answers)) {
275 return;
276 }
277 $answer = $this->answers[$index];
278 if ($answer->hasImage()) {
279 $this->deleteImage($answer->getImage());
280 }
281 unset($this->answers[$index]);
282 $this->answers = array_values($this->answers);
283 for ($i = 0, $iMax = count($this->answers); $i < $iMax; $i++) {
284 if ($this->answers[$i]->getOrder() > $index) {
285 $this->answers[$i]->setOrder($i);
286 }
287 }
288 }
289
296 public function flushAnswers(): void
297 {
298 $this->answers = [];
299 }
300
307 public function getMaximumPoints(): float
308 {
309 $points = 0;
310 foreach ($this->answers as $key => $value) {
311 if ($value->getPoints() > $points) {
312 $points = $value->getPoints();
313 }
314 }
315 return $points;
316 }
317
318 public function calculateReachedPoints(
319 int $active_id,
320 ?int $pass = null,
321 bool $authorized_solution = true
322 ): float {
323 $found_values = [];
324 if (is_null($pass)) {
325 $pass = $this->getSolutionMaxPass($active_id);
326 }
327 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
328 while ($data = $this->db->fetchAssoc($result)) {
329 if ($data['value1'] !== '') {
330 array_push($found_values, $data['value1']);
331 }
332 }
333 $points = 0.0;
334 foreach ($this->answers as $key => $answer) {
335 if ($found_values !== []
336 && in_array($key, $found_values)) {
337 $points += $answer->getPoints();
338 }
339 }
340
341 return $points;
342 }
343
345 ilAssQuestionPreviewSession $preview_session
346 ): float {
347 $participant_solution = $preview_session->getParticipantsSolution();
348
349 $points = 0.0;
350 foreach ($this->answers as $key => $answer) {
351 if (is_numeric($participant_solution)
352 && $key === (int) $participant_solution) {
353 $points = $answer->getPoints();
354 }
355 }
356
357 return $this->ensureNonNegativePoints($points);
358 }
359
360 public function saveWorkingData(
361 int $active_id,
362 ?int $pass = null,
363 bool $authorized = true
364 ): bool {
365 if (is_null($pass)) {
366 $pass = ilObjTest::_getPass($active_id);
367 }
368
369 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
370 function () use ($active_id, $pass, $authorized) {
371 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
372
373 $update = -1;
374 if ($this->db->numRows($result)) {
375 $row = $this->db->fetchAssoc($result);
376 $update = $row["solution_id"];
377 }
378
379 $multiple_choice_result = $this->http->wrapper()->post()->has('multiple_choice_result') ?
380 $this->http->wrapper()->post()->retrieve('multiple_choice_result', $this->refinery->kindlyTo()->string()) :
381 '';
382
383 if ($update !== -1
384 && $multiple_choice_result === '') {
385 $this->removeSolutionRecordById($update);
386 return;
387 }
388
389 if ($update !== -1) {
390 $this->updateCurrentSolution($update, $multiple_choice_result, null, $authorized);
391 return;
392 }
393
394 if ($multiple_choice_result !== '') {
395 $this->saveCurrentSolution($active_id, $pass, $multiple_choice_result, null, $authorized);
396 }
397 }
398 );
399
400 return true;
401 }
402
403 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
404 {
405 $mc_result_key = 'multiple_choice_result' . $this->getId() . 'ID';
406 if (
407 $this->http->wrapper()->post()->has($mc_result_key) &&
408 ($mc_result = $this->http->wrapper()->post()->retrieve($mc_result_key, $this->refinery->kindlyTo()->string())) !== ''
409 ) {
410 $previewSession->setParticipantsSolution($mc_result);
411 } else {
412 $previewSession->setParticipantsSolution(null);
413 }
414 }
415
416 public function saveAdditionalQuestionDataToDb(): void
417 {
418 // save additional data
419 $this->db->manipulateF(
420 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
421 [ "integer" ],
422 [ $this->getId() ]
423 );
424
425 $this->db->manipulateF(
426 "INSERT INTO " . $this->getAdditionalTableName(
427 ) . " (question_fi, shuffle, allow_images, thumb_size) VALUES (%s, %s, %s, %s)",
428 [ "integer", "text", "text", "integer" ],
429 [
430 $this->getId(),
431 $this->getShuffle(),
432 $this->is_singleline ? '0' : '1',
433 $this->getThumbSize()
434 ]
435 );
436 }
437
443 public function saveAnswerSpecificDataToDb(): void
444 {
445 if (!$this->is_singleline) {
446 ilFileUtils::delDir($this->getImagePath());
447 }
448 // Get all feedback entries
449 $result = $this->db->queryF(
450 "SELECT * FROM qpl_fb_specific WHERE question_fi = %s",
451 ['integer'],
452 [$this->getId()]
453 );
454 $db_feedback = $this->db->fetchAll($result);
455
456 // Check if feedback exists and the regular editor is used and not the page editor
457 if (sizeof($db_feedback) >= 1 && $this->getAdditionalContentEditingMode() == 'default') {
458 // Get all existing answer data for question
459 $result = $this->db->queryF(
460 "SELECT answer_id, aorder FROM qpl_a_sc WHERE question_fi = %s",
461 ['integer'],
462 [$this->getId()]
463 );
464 $db_answers = $this->db->fetchAll($result);
465
466 // Collect old and new order entries by ids and order to calculate a diff/intersection and remove/update feedback
467 $post_answer_order_for_id = [];
468 foreach ($this->answers as $answer) {
469 // Only the first appearance of an id is used
470 if ($answer->getId() !== null && !in_array($answer->getId(), array_keys($post_answer_order_for_id))) {
471 // -1 is happening while import and also if a new multi line answer is generated
472 if ($answer->getId() == -1) {
473 continue;
474 }
475 $post_answer_order_for_id[$answer->getId()] = $answer->getOrder();
476 }
477 }
478
479 // If there is no usable ids from post, it's better to not touch the feedback
480 // This is useful since the import is also using this function or the first creation of a new question in general
481 if (sizeof($post_answer_order_for_id) >= 1) {
482 $db_answer_order_for_id = [];
483 $db_answer_id_for_order = [];
484 foreach ($db_answers as $db_answer) {
485 $db_answer_order_for_id[intval($db_answer['answer_id'])] = intval($db_answer['aorder']);
486 $db_answer_id_for_order[intval($db_answer['aorder'])] = intval($db_answer['answer_id']);
487 }
488
489 // Handle feedback
490 // the diff between the already existing answer ids from the Database and the answer ids from post
491 // feedback related to the answer ids should be deleted or in our case not recreated.
492 $db_answer_ids = array_keys($db_answer_order_for_id);
493 $post_answer_ids = array_keys($post_answer_order_for_id);
494 $diff_db_post_answer_ids = array_diff($db_answer_ids, $post_answer_ids);
495 $unused_answer_ids = array_keys($diff_db_post_answer_ids);
496
497 // Delete all feedback in the database
498 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($this->getId(), false);
499 // Recreate feedback
500 foreach ($db_feedback as $feedback_option) {
501 // skip feedback which answer is deleted
502 if (in_array(intval($feedback_option['answer']), $unused_answer_ids)) {
503 continue;
504 }
505
506 // Reorder feedback
507 $feedback_order_db = intval($feedback_option['answer']);
508 $db_answer_id = $db_answer_id_for_order[$feedback_order_db] ?? null;
509 // This cuts feedback that currently would have no corresponding answer
510 // This case can happen while copying "broken" questions
511 // Or when saving a question with less answers than feedback
512 if (is_null($db_answer_id) || $db_answer_id < 0) {
513 continue;
514 }
515 $feedback_order_post = $post_answer_order_for_id[$db_answer_id];
516 $feedback_option['answer'] = $feedback_order_post;
517
518 // Recreate remaining feedback in database
519 $next_id = $this->db->nextId('qpl_fb_specific');
520 $this->db->manipulateF(
521 "INSERT INTO qpl_fb_specific (feedback_id, question_fi, answer, tstamp, feedback, question)
522 VALUES (%s, %s, %s, %s, %s, %s)",
523 ['integer', 'integer', 'integer', 'integer', 'text', 'integer'],
524 [
525 $next_id,
526 $feedback_option['question_fi'],
527 $feedback_option['answer'],
528 time(),
529 $feedback_option['feedback'],
530 $feedback_option['question']
531 ]
532 );
533 }
534 }
535 }
536
537 // Delete all entries in qpl_a_sc for question
538 $this->db->manipulateF(
539 "DELETE FROM qpl_a_sc WHERE question_fi = %s",
540 ['integer'],
541 [$this->getId()]
542 );
543
544 // Recreate answers one by one
545 foreach ($this->answers as $key => $value) {
547 $answer_obj = $this->answers[$key];
548 $next_id = $this->db->nextId('qpl_a_sc');
549 $this->db->manipulateF(
550 "INSERT INTO qpl_a_sc (answer_id, question_fi, answertext, points, aorder, imagefile, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
551 ['integer', 'integer', 'text', 'float', 'integer', 'text', 'integer'],
552 [
553 $next_id,
554 $this->getId(),
555 ilRTE::_replaceMediaObjectImageSrc($answer_obj->getAnswertext(), 0),
556 $answer_obj->getPoints(),
557 $answer_obj->getOrder(),
558 $answer_obj->getImage(),
559 time()
560 ]
561 );
562 }
563 }
564
571 public function getQuestionType(): string
572 {
573 return "assSingleChoice";
574 }
575
582 public function getAdditionalTableName(): string
583 {
584 return "qpl_qst_sc";
585 }
586
593 public function getAnswerTableName(): string
594 {
595 return "qpl_a_sc";
596 }
597
606 public function setImageFile(
607 string $image_filename,
608 string $image_tempfilename = ''
609 ): int {
610 if (empty($image_tempfilename)) {
611 return 0;
612 }
613
614 $cleaned_image_filename = str_replace(" ", "_", $image_filename);
615 $imagepath = $this->getImagePath();
616 if (!file_exists($imagepath)) {
617 ilFileUtils::makeDirParents($imagepath);
618 }
619
620 if (!ilFileUtils::moveUploadedFile($image_tempfilename, $cleaned_image_filename, $imagepath . $cleaned_image_filename)) {
621 return 2;
622 }
623
624 $mimetype = ilObjMediaObject::getMimeType($imagepath . $cleaned_image_filename);
625 if (!preg_match("/^image/", $mimetype)) {
626 unlink($imagepath . $cleaned_image_filename);
627 return 1;
628 }
629
630 if ($this->is_singleline && $this->getThumbSize()) {
631 $this->generateThumbForFile(
632 $cleaned_image_filename,
633 $this->getImagePath(),
634 $this->getThumbSize()
635 );
636 }
637
638 return 0;
639 }
640
641 /*
642 * Collects all text in the question which could contain media objects
643 * which were created with the Rich Text Editor
644 */
645 public function getRTETextWithMediaObjects(): string
646 {
647 $text = parent::getRTETextWithMediaObjects();
648 foreach (array_keys($this->answers) as $index) {
649 $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), 0, $index);
650 $answer_obj = $this->answers[$index];
651 $text .= $answer_obj->getAnswertext();
652 }
653 return $text;
654 }
655
659 public function getAnswers(): array
660 {
661 return $this->answers;
662 }
663
664 public function setAnswers(array $answers): void
665 {
666 $this->answers = $answers;
667 }
668
671 ): void {
672 foreach ($this->getAnswers() as $answer) {
673 /* @var ASS_AnswerBinaryStateImage $answer */
674 $answer->setAnswertext($migrator->migrateToLmContent($answer->getAnswertext()));
675 }
676 }
677
681 public function toJSON(): string
682 {
683 $result = [];
684 $result['id'] = $this->getId();
685 $result['type'] = (string) $this->getQuestionType();
686 $result['title'] = $this->getTitleForHTMLOutput();
687 $result['question'] = $this->formatSAQuestion($this->getQuestion());
688 $result['nr_of_tries'] = $this->getNrOfTries();
689 $result['shuffle'] = $this->getShuffle();
690
691 $result['feedback'] = [
692 'onenotcorrect' => $this->formatSAQuestion(
693 $this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)
694 ),
695 'allcorrect' => $this->formatSAQuestion(
696 $this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true)
697 )
698 ];
699
700 $answers = [];
701 $has_image = false;
702 foreach ($this->getAnswers() as $key => $answer_obj) {
703 if ((string) $answer_obj->getImage()) {
704 $has_image = true;
705 }
706 array_push($answers, [
707 "answertext" => $this->formatSAQuestion($answer_obj->getAnswertext()),
708 'html_id' => $this->getId() . '_' . $key,
709 "points" => (float) $answer_obj->getPoints(),
710 "order" => (int) $answer_obj->getOrder(),
711 "image" => (string) $answer_obj->getImage(),
712 "feedback" => $this->formatSAQuestion(
713 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation(
714 $this->getId(),
715 0,
716 $key
717 )
718 )
719 ]);
720 }
721 $result['answers'] = $answers;
722 if ($has_image) {
723 $result['path'] = $this->getImagePathWeb();
724 $result['thumb'] = $this->getThumbSize();
725 }
726
727 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
728 $result['mobs'] = $mobs;
729
730 return json_encode($result);
731 }
732
733 public function removeAnswerImage(int $index): void
734 {
735 $answer = $this->answers[$index];
736 if (is_object($answer)) {
737 $this->deleteImage($answer->getImage());
738 $answer->setImage(null);
739 }
740 }
741
743 {
744 return $this->current_user->getPref('tst_multiline_answers') === '1' ? 1 : 0;
745 }
746
747 public function setMultilineAnswerSetting($setting = '0'): void
748 {
749 $this->current_user->writePref('tst_multiline_answers', (string) $setting);
750 }
751
759 public function setSpecificFeedbackSetting(int $feedback_setting): void
760 {
761 $this->feedback_setting = $feedback_setting;
762 }
763
765 {
766 return $this->feedback_setting;
767 }
768
770 {
771 return 'feedback_correct_sc_mc';
772 }
773
774 public function getOperators(string $expression): array
775 {
777 }
778
779 public function getExpressionTypes(): array
780 {
781 return [
785 ];
786 }
787
788 public function getUserQuestionResult(
789 int $active_id,
790 int $pass
792 $result = new ilUserQuestionResult($this, $active_id, $pass);
793
794 $maxStep = $this->lookupMaxStep($active_id, $pass);
795 if ($maxStep > 0) {
796 $data = $this->db->queryF(
797 "SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
798 ["integer", "integer", "integer","integer"],
799 [$active_id, $pass, $this->getId(), $maxStep]
800 );
801 } else {
802 $data = $this->db->queryF(
803 "SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
804 ["integer", "integer", "integer"],
805 [$active_id, $pass, $this->getId()]
806 );
807 }
808
809 $row = $this->db->fetchAssoc($data);
810
811 if ($row != null) {
812 ++$row["value1"];
813 $result->addKeyValue($row["value1"], $row["value1"]);
814 }
815
816 $points = $this->calculateReachedPoints($active_id, $pass);
817 $max_points = $this->getMaximumPoints();
818
819 $result->setReachedPercentage(($points / $max_points) * 100);
820
821 return $result;
822 }
823
830 public function getAvailableAnswerOptions($index = null)
831 {
832 if ($index !== null) {
833 return $this->getAnswer($index);
834 } else {
835 return $this->getAnswers();
836 }
837 }
838
842 protected function afterSyncWithOriginal(
843 int $original_question_id,
844 int $clone_question_id,
845 int $original_parent_id,
846 int $clone_parent_id
847 ): void {
848 parent::afterSyncWithOriginal($original_question_id, $clone_question_id, $original_parent_id, $clone_parent_id);
849
850 $original_image_path = $this->question_files->buildImagePath($original_question_id, $original_parent_id);
851 $clone_image_path = $this->question_files->buildImagePath($clone_question_id, $clone_parent_id);
852
853 ilFileUtils::delDir($original_image_path);
854 if (is_dir($clone_image_path)) {
855 ilFileUtils::makeDirParents($original_image_path);
856 ilFileUtils::rCopy($clone_image_path, $original_image_path);
857 }
858 }
859
860 public function isSingleline(): bool
861 {
862 return $this->is_singleline;
863 }
864
865 public function setIsSingleline(bool $is_singleline): void
866 {
867 $this->is_singleline = $is_singleline;
868 }
869
870 public function getFeedbackSetting(): int
871 {
872 return $this->feedback_setting;
873 }
874
875 public function setFeedbackSetting(int $feedback_setting): void
876 {
877 $this->feedback_setting = $feedback_setting;
878 }
879
880 public function toLog(AdditionalInformationGenerator $additional_info): array
881 {
882 $result = [
883 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
884 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
885 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
886 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
887 ->getTrueFalseTagForBool($this->getShuffle()),
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_REACHABLE_POINTS => (float) $answer_obj->getPoints(),
898 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => (int) $answer_obj->getOrder(),
899 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_IMAGE => (string) $answer_obj->getImage(),
900 AdditionalInformationGenerator::KEY_FEEDBACK => $this->formatSAQuestion(
901 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
902 )
903 ];
904 }
905
906 return $result;
907 }
908
909 protected function solutionValuesToLog(
910 AdditionalInformationGenerator $additional_info,
911 array $solution_values
912 ): string {
913 if ($solution_values === []
914 || !array_key_exists(0, $solution_values)
915 || !is_array($solution_values[0])) {
916 return $additional_info->getNoneTag();
917 }
918
919 return $this->getAnswer((int) $solution_values[0]['value1'])->getAnswertext();
920 }
921
922 public function solutionValuesToText(array $solution_values): string
923 {
924 if ($solution_values === []
925 || !array_key_exists(0, $solution_values)
926 || !is_array($solution_values[0])) {
927 return '';
928 }
929
930 return $this->getAnswer((int) $solution_values[0]['value1'])->getAnswertext();
931 }
932
933 public function getCorrectSolutionForTextOutput(int $active_id, int $pass): array
934 {
935 return array_reduce(
936 $this->getAnswers(),
937 function (array $c, ASS_AnswerBinaryStateImage $v): array {
938 if ($v->getPoints() > 0.0) {
939 $c[] = $v->getAnswertext()
940 . "({$this->lng->txt('points')}: {$v->getPoints()})";
941 }
942 return $c;
943 },
944 []
945 );
946 }
947}
Class for answers with a binary state indicator.
getPoints()
Gets the points.
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)
Class for single choice questions.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
setSpecificFeedbackSetting(int $feedback_setting)
Sets the feedback settings in effect for the question.
getQuestionType()
Returns the question type of the question.
getAnswerCount()
Returns the number of answers.
setMultilineAnswerSetting($setting='0')
toJSON()
Returns a JSON representation of the question.
setFeedbackSetting(int $feedback_setting)
afterSyncWithOriginal(int $original_question_id, int $clone_question_id, int $original_parent_id, int $clone_parent_id)
{}
addAnswer(string $answertext='', float $points=0.0, int $order=0, ?string $answerimage=null, int $answer_id=-1)
setImageFile(string $image_filename, string $image_tempfilename='')
Sets the image file and uploads the image to the object's image directory.
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
getAnswer(int $index=0)
Returns an answer with a given index.
getCorrectSolutionForTextOutput(int $active_id, int $pass)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="", int $output_type=self::OUTPUT_ORDER)
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
deleteAnswer(int $index=0)
Deletes an answer with a given index.
getExpressionTypes()
Get all available expression types for a specific question.
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
cloneQuestionTypeSpecificProperties(\assQuestion $target)
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.
loadFromDb(int $question_id)
saveToDb(?int $original_id=null)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
getAnswers()
Returns a reference to the answers array.
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
getOperators(string $expression)
Get all available operations for a specific question.
flushAnswers()
Deletes all answers.
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
setAnswers(array $answers)
setIsSingleline(bool $is_singleline)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
getAnswerTableName()
Returns the name of the answer table in the database.
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 rCopy(string $a_sdir, string $a_tdir, bool $preserveTimeAttributes=false)
Copies content of a directory $a_sdir recursively to a directory $a_tdir.
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 _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
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...
$c
Definition: deliver.php:25
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...
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static http()
Fetches the global http state from ILIAS.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
if(!file_exists('../ilias.ini.php'))