ILIAS  release_8 Revision v8.24
class.assKprimChoice.php
Go to the documentation of this file.
1<?php
2
26{
27 public const NUM_REQUIRED_ANSWERS = 4;
28
30
31 public const ANSWER_TYPE_SINGLE_LINE = 'singleLine';
32 public const ANSWER_TYPE_MULTI_LINE = 'multiLine';
33
34 public const OPTION_LABEL_RIGHT_WRONG = 'right_wrong';
35 public const OPTION_LABEL_PLUS_MINUS = 'plus_minus';
36 public const OPTION_LABEL_APPLICABLE_OR_NOT = 'applicable_or_not';
37 public const OPTION_LABEL_ADEQUATE_OR_NOT = 'adequate_or_not';
38 public const OPTION_LABEL_CUSTOM = 'customlabel';
39
40 public const DEFAULT_THUMB_SIZE = 150;
41 public const THUMB_PREFIX = 'thumb.';
42
44
45 private $answerType;
46
47 private $thumbSize;
48
50
51 private $optionLabel;
52
54
56
58
59 private $answers;
60
61 public function __construct($title = '', $comment = '', $author = '', $owner = -1, $question = '')
62 {
64
65 $this->shuffleAnswersEnabled = true;
66 $this->answerType = self::ANSWER_TYPE_SINGLE_LINE;
67 $this->thumbSize = self::DEFAULT_THUMB_SIZE;
68 $this->scorePartialSolutionEnabled = true;
69 $this->optionLabel = self::OPTION_LABEL_RIGHT_WRONG;
70 $this->customTrueOptionLabel = '';
71 $this->customFalseOptionLabel = '';
72
74
75 $this->answers = array();
76 }
77
78 public function getQuestionType(): string
79 {
80 return 'assKprimChoice';
81 }
82
83 public function getAdditionalTableName(): string
84 {
85 return "qpl_qst_kprim";
86 }
87
88 public function getAnswerTableName(): string
89 {
90 return "qpl_a_kprim";
91 }
92
94 {
95 $this->shuffleAnswersEnabled = $shuffleAnswersEnabled;
96 }
97
98 public function isShuffleAnswersEnabled(): bool
99 {
101 }
102
103 public function setAnswerType($answerType): void
104 {
105 $this->answerType = $answerType;
106 }
107
108 public function getAnswerType(): string
109 {
110 return $this->answerType;
111 }
112
113 public function setThumbSize(int $thumbSize): void
114 {
115 $this->thumbSize = $thumbSize;
116 }
117
118 public function getThumbSize(): int
119 {
120 return $this->thumbSize;
121 }
122
124 {
125 $this->scorePartialSolutionEnabled = $scorePartialSolutionEnabled;
126 }
127
128 public function isScorePartialSolutionEnabled(): bool
129 {
131 }
132
133 public function setOptionLabel($optionLabel): void
134 {
135 $this->optionLabel = $optionLabel;
136 }
137
138 public function getOptionLabel(): string
139 {
140 return $this->optionLabel;
141 }
142
144 {
145 $this->customTrueOptionLabel = $customTrueOptionLabel;
146 }
147
148 public function getCustomTrueOptionLabel()
149 {
151 }
152
154 {
155 $this->customFalseOptionLabel = $customFalseOptionLabel;
156 }
157
159 {
161 }
162
164 {
165 $this->specificFeedbackSetting = $specificFeedbackSetting;
166 }
167
169 {
171 }
172
173 public function setAnswers($answers): void
174 {
175 if (is_null($answers)) {
176 return;
177 }
178 $clean_answer_text = function (ilAssKprimChoiceAnswer $answer) {
179 $answer->setAnswertext(
180 $this->getHtmlQuestionContentPurifier()->purify($answer->getAnswertext())
181 );
182 return $answer;
183 };
184 $this->answers = array_map($clean_answer_text, $answers);
185 }
186
187 public function getAnswers(): array
188 {
189 return $this->answers;
190 }
191
192 public function getAnswer($position)
193 {
194 foreach ($this->getAnswers() as $answer) {
195 if ($answer->getPosition() == $position) {
196 return $answer;
197 }
198 }
199
200 return null;
201 }
202
203 public function addAnswer(ilAssKprimChoiceAnswer $answer): void
204 {
205 $answer->setAnswertext(
206 $this->getHtmlQuestionContentPurifier()->purify($answer->getAnswertext())
207 );
208 $this->answers[] = $answer;
209 }
210
211 public function loadFromDb($questionId): void
212 {
213 $res = $this->db->queryF($this->buildQuestionDataQuery(), array('integer'), array($questionId));
214
215 while ($data = $this->db->fetchAssoc($res)) {
216 $this->setId($questionId);
217
218 $this->setOriginalId($data['original_id']);
219
220 $this->setObjId($data['obj_fi']);
221
222 $this->setTitle($data['title'] ?? '');
223 $this->setNrOfTries($data['nr_of_tries']);
224 $this->setComment($data['description'] ?? '');
225 $this->setAuthor($data['author']);
226 $this->setPoints($data['points']);
227 $this->setOwner($data['owner']);
228 $this->setLastChange($data['tstamp']);
229 require_once 'Services/RTE/classes/class.ilRTE.php';
230 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data['question_text'] ?? '', 1));
231
232 $this->setShuffleAnswersEnabled((bool) $data['shuffle_answers']);
233
234 if ($this->isValidAnswerType($data['answer_type'])) {
235 $this->setAnswerType($data['answer_type']);
236 }
237
238 if (is_numeric($data['thumb_size'])) {
239 $this->setThumbSize((int) $data['thumb_size']);
240 }
241
242 if ($this->isValidOptionLabel($data['opt_label'])) {
243 $this->setOptionLabel($data['opt_label']);
244 }
245
246 if ($data['custom_true'] !== null) {
247 $this->setCustomTrueOptionLabel($data['custom_true']);
248 }
249
250 if ($data['custom_false'] !== null) {
251 $this->setCustomFalseOptionLabel($data['custom_false']);
252 }
253
254 if ($data['score_partsol'] !== null) {
255 $this->setScorePartialSolutionEnabled((bool) $data['score_partsol']);
256 }
257
258 if (isset($data['feedback_setting'])) {
259 $this->setSpecificFeedbackSetting((int) $data['feedback_setting']);
260 }
261
262 try {
263 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
266 }
267
268 try {
269 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
271 }
272 }
273
274 $this->loadAnswerData($questionId);
275
276 parent::loadFromDb($questionId);
277 }
278
279 private function loadAnswerData($questionId): void
280 {
281 global $DIC;
282 $ilDB = $DIC['ilDB'];
283
284 $res = $this->db->queryF(
285 "SELECT * FROM {$this->getAnswerTableName()} WHERE question_fi = %s ORDER BY position ASC",
286 array('integer'),
287 array($questionId)
288 );
289
290 require_once 'Modules/TestQuestionPool/classes/class.ilAssKprimChoiceAnswer.php';
291 require_once 'Services/RTE/classes/class.ilRTE.php';
292
293 while ($data = $ilDB->fetchAssoc($res)) {
294 $answer = new ilAssKprimChoiceAnswer();
295
296 $answer->setPosition($data['position']);
297
298 $answer->setAnswertext(ilRTE::_replaceMediaObjectImageSrc($data['answertext'] ?? '', 1));
299
300 $answer->setImageFile($data['imagefile']);
301 $answer->setThumbPrefix($this->getThumbPrefix());
302 $answer->setImageFsDir($this->getImagePath());
303 $answer->setImageWebDir($this->getImagePathWeb());
304
305 $answer->setCorrectness($data['correctness']);
306
307 $this->answers[$answer->getPosition()] = $answer;
308 }
309
310 for ($i = count($this->answers); $i < self::NUM_REQUIRED_ANSWERS; $i++) {
311 $answer = new ilAssKprimChoiceAnswer();
312
313 $answer->setPosition($i);
314
315 $this->answers[$answer->getPosition()] = $answer;
316 }
317 }
318
319 public function saveToDb($originalId = ''): void
320 {
321 if ($originalId == '') {
322 $this->saveQuestionDataToDb();
323 } else {
324 $this->saveQuestionDataToDb($originalId);
325 }
326
329
330 parent::saveToDb($originalId);
331 }
332
334 {
335 $this->db->replace(
336 $this->getAdditionalTableName(),
337 array(
338 'question_fi' => array('integer', $this->getId())
339 ),
340 array(
341 'shuffle_answers' => array('integer', (int) $this->isShuffleAnswersEnabled()),
342 'answer_type' => array('text', $this->getAnswerType()),
343 'thumb_size' => array('integer', $this->getThumbSize()),
344 'opt_label' => array('text', $this->getOptionLabel()),
345 'custom_true' => array('text', $this->getCustomTrueOptionLabel()),
346 'custom_false' => array('text', $this->getCustomFalseOptionLabel()),
347 'score_partsol' => array('integer', (int) $this->isScorePartialSolutionEnabled()),
348 'feedback_setting' => array('integer', $this->getSpecificFeedbackSetting())
349 )
350 );
351 }
352
354 {
355 foreach ($this->getAnswers() as $answer) {
356 $this->db->replace(
357 $this->getAnswerTableName(),
358 array(
359 'question_fi' => array('integer', $this->getId()),
360 'position' => array('integer', (int) $answer->getPosition())
361 ),
362 array(
363 'answertext' => array('text', $answer->getAnswertext()),
364 'imagefile' => array('text', $answer->getImageFile()),
365 'correctness' => array('integer', (int) $answer->getCorrectness())
366 )
367 );
368 }
369
370 $this->rebuildThumbnails();
371 }
372
373 public function isComplete(): bool
374 {
375 foreach (array($this->title, $this->author, $this->question) as $text) {
376 if (!strlen($text)) {
377 return false;
378 }
379 }
380
381 if (!isset($this->points)) {
382 return false;
383 }
384
385 foreach ($this->getAnswers() as $answer) {
386 /* @var ilAssKprimChoiceAnswer $answer */
387
388 if (is_null($answer->getCorrectness())) {
389 return false;
390 }
391
392 if (
393 (!is_string($answer->getAnswertext()) || $answer->getAnswertext() === '') &&
394 (!is_string($answer->getImageFile()) || $answer->getImageFile() === '')
395 ) {
396 return false;
397 }
398 }
399
400 return true;
401 }
402
411 public function saveWorkingData($active_id, $pass = null, $authorized = true): bool
412 {
414 $ilDB = $GLOBALS['DIC']['ilDB'];
415
416 if (is_null($pass)) {
417 include_once "./Modules/Test/classes/class.ilObjTest.php";
418 $pass = ilObjTest::_getPass($active_id);
419 }
420
421 $entered_values = 0;
422
423 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
424 $this->removeCurrentSolution($active_id, $pass, $authorized);
425
426 $solutionSubmit = $this->getSolutionSubmit();
427
428 foreach ($solutionSubmit as $answerIndex => $answerValue) {
429 $this->saveCurrentSolution($active_id, $pass, (int) $answerIndex, (int) $answerValue, $authorized);
430 $entered_values++;
431 }
432 });
433
434 if ($entered_values) {
435 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
437 assQuestion::logAction($this->lng->txtlng(
438 "assessment",
439 "log_user_entered_values",
441 ), $active_id, $this->getId());
442 }
443 } else {
444 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
446 assQuestion::logAction($this->lng->txtlng(
447 "assessment",
448 "log_user_not_entered_values",
450 ), $active_id, $this->getId());
451 }
452 }
453
454 return true;
455 }
456
467 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
468 {
469 if ($returndetails) {
470 throw new ilTestException('return details not implemented for ' . __METHOD__);
471 }
472
473 global $DIC;
474 $ilDB = $DIC['ilDB'];
475
476 $found_values = array();
477 if (is_null($pass)) {
478 $pass = $this->getSolutionMaxPass($active_id);
479 }
480
481 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
482
483 while ($data = $ilDB->fetchAssoc($result)) {
484 $found_values[(int) $data['value1']] = (int) $data['value2'];
485 }
486
487 $points = $this->calculateReachedPointsForSolution($found_values, $active_id);
488
489 return $points;
490 }
491
492 public function getValidAnswerTypes(): array
493 {
494 return array(self::ANSWER_TYPE_SINGLE_LINE, self::ANSWER_TYPE_MULTI_LINE);
495 }
496
497 public function isValidAnswerType($answerType): bool
498 {
499 $validTypes = $this->getValidAnswerTypes();
500 return in_array($answerType, $validTypes);
501 }
502
503 public function isSingleLineAnswerType($answerType): bool
504 {
506 }
507
513 {
514 return array(
515 self::ANSWER_TYPE_SINGLE_LINE => $lng->txt('answers_singleline'),
516 self::ANSWER_TYPE_MULTI_LINE => $lng->txt('answers_multiline')
517 );
518 }
519
520 public function getValidOptionLabels(): array
521 {
522 return array(
523 self::OPTION_LABEL_RIGHT_WRONG,
524 self::OPTION_LABEL_PLUS_MINUS,
525 self::OPTION_LABEL_APPLICABLE_OR_NOT,
526 self::OPTION_LABEL_ADEQUATE_OR_NOT,
527 self::OPTION_LABEL_CUSTOM
528 );
529 }
530
532 {
533 return array(
534 self::OPTION_LABEL_RIGHT_WRONG => $lng->txt('option_label_right_wrong'),
535 self::OPTION_LABEL_PLUS_MINUS => $lng->txt('option_label_plus_minus'),
536 self::OPTION_LABEL_APPLICABLE_OR_NOT => $lng->txt('option_label_applicable_or_not'),
537 self::OPTION_LABEL_ADEQUATE_OR_NOT => $lng->txt('option_label_adequate_or_not'),
538 self::OPTION_LABEL_CUSTOM => $lng->txt('option_label_custom')
539 );
540 }
541
542 public function isValidOptionLabel($optionLabel): bool
543 {
544 $validLabels = $this->getValidOptionLabels();
545 return in_array($optionLabel, $validLabels);
546 }
547
549 {
550 switch ($optionLabel) {
552 return $lng->txt('option_label_right');
553
555 return $lng->txt('option_label_plus');
556
558 return $lng->txt('option_label_applicable');
559
561 return $lng->txt('option_label_adequate');
562
564 default:
565 return $this->getCustomTrueOptionLabel();
566 }
567 }
568
570 {
571 switch ($optionLabel) {
573 return $lng->txt('option_label_wrong');
574
576 return $lng->txt('option_label_minus');
577
579 return $lng->txt('option_label_not_applicable');
580
582 return $lng->txt('option_label_not_adequate');
583
585 default:
586 return $this->getCustomFalseOptionLabel();
587 }
588 }
589
591 {
592 return sprintf(
593 $lng->txt('kprim_instruction_text'),
594 $this->getTrueOptionLabelTranslation($lng, $optionLabel),
595 $this->getFalseOptionLabelTranslation($lng, $optionLabel)
596 );
597 }
598
599 public function isCustomOptionLabel($labelValue): bool
600 {
601 return $labelValue == self::OPTION_LABEL_CUSTOM;
602 }
603
604 public function getThumbPrefix(): string
605 {
606 return self::THUMB_PREFIX;
607 }
608
609 public function rebuildThumbnails(): void
610 {
611 if ($this->isSingleLineAnswerType($this->getAnswerType()) && $this->getThumbSize()) {
612 foreach ($this->getAnswers() as $answer) {
613 if (strlen($answer->getImageFile())) {
614 $this->generateThumbForFile($answer->getImageFsDir(), $answer->getImageFile());
615 }
616 }
617 }
618 }
619
620 protected function generateThumbForFile($path, $file): void
621 {
622 $filename = $path . $file;
623 if (@file_exists($filename)) {
624 $thumbpath = $path . $this->getThumbPrefix() . $file;
625 $path_info = @pathinfo($filename);
626 $ext = "";
627 switch (strtoupper($path_info['extension'])) {
628 case 'PNG':
629 $ext = 'PNG';
630 break;
631 case 'GIF':
632 $ext = 'GIF';
633 break;
634 default:
635 $ext = 'JPEG';
636 break;
637 }
638 ilShellUtil::convertImage($filename, $thumbpath, $ext, (string) $this->getThumbSize());
639 }
640 }
641
642 public function handleFileUploads($answers, $files): void
643 {
644 foreach ($answers as $answer) {
645 /* @var ilAssKprimChoiceAnswer $answer */
646
647 if (!isset($files[$answer->getPosition()])) {
648 continue;
649 }
650
651 $this->handleFileUpload($answer, $files[$answer->getPosition()]);
652 }
653 }
654
655 private function handleFileUpload(ilAssKprimChoiceAnswer $answer, $fileData): int
656 {
657 $imagePath = $this->getImagePath();
658
659 if (!file_exists($imagePath)) {
660 ilFileUtils::makeDirParents($imagePath);
661 }
662
663 $filename = $this->buildHashedImageFilename($fileData['name'], true);
664
665 $answer->setImageFsDir($imagePath);
666 $answer->setImageFile($filename);
667
668 if (!ilFileUtils::moveUploadedFile($fileData['tmp_name'], $fileData['name'], $answer->getImageFsPath())) {
669 return 2;
670 }
671
672 return 0;
673 }
674
675 public function removeAnswerImage($position): void
676 {
677 $answer = $this->getAnswer($position);
678
679 if (file_exists($answer->getImageFsPath())) {
680 ilFileUtils::delDir($answer->getImageFsPath());
681 }
682
683 if (file_exists($answer->getThumbFsPath())) {
684 ilFileUtils::delDir($answer->getThumbFsPath());
685 }
686
687 $answer->setImageFile(null);
688 }
689
690 protected function getSolutionSubmit(): array
691 {
692 $solutionSubmit = [];
693 $post = $this->dic->http()->wrapper()->post();
694
695 foreach ($this->getAnswers() as $index => $a) {
696 if ($post->has("kprim_choice_result_$index")) {
697 $value = $post->retrieve(
698 "kprim_choice_result_$index",
699 $this->dic->refinery()->kindlyTo()->string()
700 );
701 if (is_numeric($value)) {
702 $solutionSubmit[] = $value;
703 }
704 }
705 }
706 return $solutionSubmit;
707 }
708
709 protected function calculateReachedPointsForSolution($found_values, $active_id = 0)
710 {
711 $numCorrect = 0;
712 if ($found_values == null) {
713 $found_values = [];
714 }
715 foreach ($this->getAnswers() as $key => $answer) {
716 if (!isset($found_values[$answer->getPosition()])) {
717 continue;
718 }
719
720 if ($found_values[$answer->getPosition()] == $answer->getCorrectness()) {
721 $numCorrect++;
722 }
723 }
724
725 if ($numCorrect >= self::NUM_REQUIRED_ANSWERS) {
726 $points = $this->getPoints();
727 } elseif ($this->isScorePartialSolutionEnabled() && $numCorrect >= self::PARTIAL_SCORING_NUM_CORRECT_ANSWERS) {
728 $points = $this->getPoints() / 2;
729 } else {
730 $points = 0;
731 }
732
733 if ($active_id) {
734 if (count($found_values) == 0) {
735 $points = 0;
736 }
737 }
738 return $points;
739 }
740
741 public function duplicate(bool $for_test = true, string $title = "", string $author = "", string $owner = "", $testObjId = null): int
742 {
743 if ($this->id <= 0) {
744 // The question has not been saved. It cannot be duplicated
745 return -1;
746 }
747 // duplicate the question in database
748 $this_id = $this->getId();
749 $thisObjId = $this->getObjId();
750
751 $clone = $this;
752 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
754 $clone->id = -1;
755
756 if ((int) $testObjId > 0) {
757 $clone->setObjId($testObjId);
758 }
759
760 if ($title) {
761 $clone->setTitle($title);
762 }
763
764 if ($author) {
765 $clone->setAuthor($author);
766 }
767 if ($owner) {
768 $clone->setOwner($owner);
769 }
770
771 if ($for_test) {
772 $clone->saveToDb($original_id);
773 } else {
774 $clone->saveToDb();
775 }
776
777 // copy question page content
778 $clone->copyPageOfQuestion($this_id);
779 // copy XHTML media objects
780 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
781 // duplicate the images
782 $clone->cloneAnswerImages($this_id, $thisObjId, $clone->getId(), $clone->getObjId());
783
784 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
785
786 return $clone->id;
787 }
788
789 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = ""): int
790 {
791 if ($this->getId() <= 0) {
792 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
793 }
794
795 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
796
797 $sourceQuestionId = $this->id;
798 $sourceParentId = $this->getObjId();
799
800 // duplicate the question in database
801 $clone = $this;
802 $clone->id = -1;
803
804 $clone->setObjId($targetParentId);
805
806 if ($targetQuestionTitle) {
807 $clone->setTitle($targetQuestionTitle);
808 }
809
810 $clone->saveToDb();
811 // copy question page content
812 $clone->copyPageOfQuestion($sourceQuestionId);
813 // copy XHTML media objects
814 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
815 // duplicate the image
816 $clone->cloneAnswerImages($sourceQuestionId, $sourceParentId, $clone->getId(), $clone->getObjId());
817
818 $clone->onCopy($sourceParentId, $sourceQuestionId, $targetParentId, $clone->getId());
819
820 return $clone->id;
821 }
822
826 public function copyObject($target_questionpool_id, $title = ""): int
827 {
828 if ($this->getId() <= 0) {
829 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
830 }
831 // duplicate the question in database
832 $clone = $this;
833 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
835 $clone->id = -1;
836 $source_questionpool_id = $this->getObjId();
837 $clone->setObjId($target_questionpool_id);
838 if ($title) {
839 $clone->setTitle($title);
840 }
841 $clone->saveToDb();
842 // copy question page content
843 $clone->copyPageOfQuestion($original_id);
844 // copy XHTML media objects
845 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
846 // duplicate the image
847 $clone->cloneAnswerImages($original_id, $source_questionpool_id, $clone->getId(), $clone->getObjId());
848
849 $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
850
851 return $clone->id;
852 }
853
854 protected function beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId): void
855 {
856 parent::beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId);
857
858 $question = self::instantiateQuestion($origQuestionId);
859
860 foreach ($question->getAnswers() as $answer) {
861 $question->removeAnswerImage($answer->getPosition());
862 }
863 }
864
865 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId): void
866 {
867 parent::afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId);
868
869 $this->cloneAnswerImages($dupQuestionId, $dupParentObjId, $origQuestionId, $origParentObjId);
870 }
871
872 protected function cloneAnswerImages($sourceQuestionId, $sourceParentId, $targetQuestionId, $targetParentId): void
873 {
875 global $DIC;
876 $ilLog = $DIC['ilLog'];
877
878 $sourcePath = $this->buildImagePath($sourceQuestionId, $sourceParentId);
879 $targetPath = $this->buildImagePath($targetQuestionId, $targetParentId);
880
881 foreach ($this->getAnswers() as $answer) {
882 $filename = $answer->getImageFile();
883
884 if (strlen($filename)) {
885 if (!file_exists($targetPath)) {
886 ilFileUtils::makeDirParents($targetPath);
887 }
888
889 if (file_exists($sourcePath . $filename)) {
890 if (!copy($sourcePath . $filename, $targetPath . $filename)) {
891 $ilLog->warning(sprintf(
892 "Could not clone source image '%s' to '%s' (srcQuestionId: %s|tgtQuestionId: %s|srcParentObjId: %s|tgtParentObjId: %s)",
893 $sourcePath . $filename,
894 $targetPath . $filename,
895 $sourceQuestionId,
896 $targetQuestionId,
897 $sourceParentId,
898 $targetParentId
899 ));
900 }
901 }
902
903 if (file_exists($sourcePath . $this->getThumbPrefix() . $filename)) {
904 if (!copy($sourcePath . $this->getThumbPrefix() . $filename, $targetPath . $this->getThumbPrefix() . $filename)) {
905 $ilLog->warning(sprintf(
906 "Could not clone thumbnail source image '%s' to '%s' (srcQuestionId: %s|tgtQuestionId: %s|srcParentObjId: %s|tgtParentObjId: %s)",
907 $sourcePath . $this->getThumbPrefix() . $filename,
908 $targetPath . $this->getThumbPrefix() . $filename,
909 $sourceQuestionId,
910 $targetQuestionId,
911 $sourceParentId,
912 $targetParentId
913 ));
914 }
915 }
916 }
917 }
918 }
919
920 protected function getRTETextWithMediaObjects(): string
921 {
922 $combinedText = parent::getRTETextWithMediaObjects();
923
924 foreach ($this->getAnswers() as $answer) {
925 $combinedText .= $answer->getAnswertext();
926 }
927
928 return $combinedText;
929 }
930
935 {
936 foreach ($this->getAnswers() as $answer) {
937 /* @var ilAssKprimChoiceAnswer $answer */
938 $answer->setAnswertext($migrator->migrateToLmContent($answer->getAnswertext()));
939 }
940 }
941
945 public function toJSON(): string
946 {
947 $this->lng->loadLanguageModule('assessment');
948
949 require_once './Services/RTE/classes/class.ilRTE.php';
950 $result = array();
951 $result['id'] = $this->getId();
952 $result['type'] = $this->getQuestionType();
953 $result['title'] = $this->getTitleForHTMLOutput();
954 $result['question'] = $this->formatSAQuestion($this->getQuestion());
955 $result['instruction'] = $this->getInstructionTextTranslation(
956 $this->lng,
957 $this->getOptionLabel()
958 );
959 $result['nr_of_tries'] = $this->getNrOfTries();
960 $result['shuffle'] = $this->isShuffleAnswersEnabled();
961 $result['feedback'] = array(
962 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
963 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
964 );
965
966 $result['trueOptionLabel'] = $this->getTrueOptionLabelTranslation($this->lng, $this->getOptionLabel());
967 $result['falseOptionLabel'] = $this->getFalseOptionLabelTranslation($this->lng, $this->getOptionLabel());
968
969 $result['num_allowed_failures'] = $this->getNumAllowedFailures();
970
971 $answers = array();
972 $has_image = false;
973
974 foreach ($this->getAnswers() as $key => $answer) {
975 if (strlen((string) $answer->getImageFile())) {
976 $has_image = true;
977 }
978
979 $answers[] = array(
980 'answertext' => $this->formatSAQuestion($answer->getAnswertext() ?? ''),
981 'correctness' => (bool) $answer->getCorrectness(),
982 'order' => (int) $answer->getPosition(),
983 'image' => (string) $answer->getImageFile(),
984 'feedback' => $this->formatSAQuestion(
985 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
986 )
987 );
988 }
989
990 $result['answers'] = $answers;
991
992 if ($has_image) {
993 $result['path'] = $this->getImagePathWeb();
994 $result['thumb'] = $this->getThumbSize();
995 }
996
997 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
998 $result['mobs'] = $mobs;
999
1000 return json_encode($result);
1001 }
1002
1003 private function getNumAllowedFailures(): int
1004 {
1005 if ($this->isScorePartialSolutionEnabled()) {
1006 return self::NUM_REQUIRED_ANSWERS - self::PARTIAL_SCORING_NUM_CORRECT_ANSWERS;
1007 }
1008
1009 return 0;
1010 }
1011
1013 {
1014 return 'feedback_correct_kprim';
1015 }
1016
1017 public static function isObligationPossible(int $questionId): bool
1018 {
1019 return true;
1020 }
1021
1022 public function isAnswered(int $active_id, int $pass): bool
1023 {
1024 $numExistingSolutionRecords = assQuestion::getNumExistingSolutionRecords($active_id, $pass, $this->getId());
1025
1026 return $numExistingSolutionRecords >= 4;
1027 }
1028
1032 public function setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass): int
1033 {
1034 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1035
1036 $solution = $this->getSolutionValues($active_id, $pass);
1037
1038 $i = 1;
1039 foreach ($this->getAnswers() as $id => $answer) {
1040 $worksheet->setCell($startrow + $i, 0, $answer->getAnswertext());
1041 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1042 $correctness = false;
1043 foreach ($solution as $solutionvalue) {
1044 if ($id == $solutionvalue['value1']) {
1045 $correctness = $solutionvalue['value2'];
1046 break;
1047 }
1048 }
1049 $worksheet->setCell($startrow + $i, 2, $correctness);
1050 $i++;
1051 }
1052
1053 return $startrow + $i + 1;
1054 }
1055
1056 public function moveAnswerDown($position): bool
1057 {
1058 if ($position < 0 || $position >= (self::NUM_REQUIRED_ANSWERS - 1)) {
1059 return false;
1060 }
1061
1062 for ($i = 0, $max = count($this->answers); $i < $max; $i++) {
1063 if ($i == $position) {
1064 $movingAnswer = $this->answers[$i];
1065 $targetAnswer = $this->answers[ $i + 1 ];
1066
1067 $movingAnswer->setPosition($position + 1);
1068 $targetAnswer->setPosition($position);
1069
1070 $this->answers[ $i + 1 ] = $movingAnswer;
1071 $this->answers[$i] = $targetAnswer;
1072 }
1073 }
1074 return true;
1075 }
1076
1077 public function moveAnswerUp($position): bool
1078 {
1079 if ($position <= 0 || $position > (self::NUM_REQUIRED_ANSWERS - 1)) {
1080 return false;
1081 }
1082
1083 for ($i = 0, $max = count($this->answers); $i < $max; $i++) {
1084 if ($i == $position) {
1085 $movingAnswer = $this->answers[$i];
1086 $targetAnswer = $this->answers[ $i - 1 ];
1087
1088 $movingAnswer->setPosition($position - 1);
1089 $targetAnswer->setPosition($position);
1090
1091 $this->answers[ $i - 1 ] = $movingAnswer;
1092 $this->answers[$i] = $targetAnswer;
1093 }
1094 }
1095
1096 return true;
1097 }
1098}
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
$filename
Definition: buildRTE.php:78
getQuestionType()
Returns the question type of the question.
const PARTIAL_SCORING_NUM_CORRECT_ANSWERS
toJSON()
Returns a JSON representation of the question.
beforeSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
isSingleLineAnswerType($answerType)
setCustomTrueOptionLabel($customTrueOptionLabel)
setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass)
{}
handleFileUpload(ilAssKprimChoiceAnswer $answer, $fileData)
handleFileUploads($answers, $files)
setCustomFalseOptionLabel($customFalseOptionLabel)
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
addAnswer(ilAssKprimChoiceAnswer $answer)
generateThumbForFile($path, $file)
removeAnswerImage($position)
copyObject($target_questionpool_id, $title="")
Copies an assMultipleChoice object.
afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
loadAnswerData($questionId)
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
setSpecificFeedbackSetting($specificFeedbackSetting)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
getValidOptionLabelsTranslated(ilLanguage $lng)
getTrueOptionLabelTranslation(ilLanguage $lng, $optionLabel)
setAnswerType($answerType)
saveToDb($originalId='')
getFalseOptionLabelTranslation(ilLanguage $lng, $optionLabel)
calculateReachedPointsForSolution($found_values, $active_id=0)
isValidOptionLabel($optionLabel)
isCustomOptionLabel($labelValue)
isAnswered(int $active_id, int $pass)
isValidAnswerType($answerType)
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
getInstructionTextTranslation(ilLanguage $lng, $optionLabel)
setOptionLabel($optionLabel)
static isObligationPossible(int $questionId)
duplicate(bool $for_test=true, string $title="", string $author="", string $owner="", $testObjId=null)
isComplete()
Returns true, if a question is complete for use.
getAnswerTypeSelectOptions(ilLanguage $lng)
setShuffleAnswersEnabled($shuffleAnswersEnabled)
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
const OPTION_LABEL_APPLICABLE_OR_NOT
__construct($title='', $comment='', $author='', $owner=-1, $question='')
setScorePartialSolutionEnabled($scorePartialSolutionEnabled)
setThumbSize(int $thumbSize)
Abstract basic class which is to be extended by the concrete assessment question type classes.
float $points
The maximum available points for the question.
setOriginalId(?int $original_id)
string $question
The question text.
static logAction(string $logtext, int $active_id, int $question_id)
setId(int $id=-1)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
buildHashedImageFilename(string $plain_image_filename, bool $unique=false)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
setQuestion(string $question="")
buildImagePath($questionId, $parentObjectId)
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
static _getOriginalId(int $question_id)
ILIAS DI LoggingServices $ilLog
saveQuestionDataToDb(int $original_id=-1)
setAuthor(string $author="")
setComment(string $comment="")
setObjId(int $obj_id=0)
getSolutionMaxPass(int $active_id)
setOwner(int $owner=-1)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
static getNumExistingSolutionRecords(int $activeId, int $pass, int $questionId)
static instantiateQuestion(int $question_id)
saveWorkingData(int $active_id, int $pass, bool $authorized=true)
Saves the learners input of the question to the database.
setTitle(string $title="")
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
getImagePathWeb()
Returns the web image path for web accessable images of a question.
setLastChange($lastChange)
setPoints(float $points)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setCell($a_row, $a_col, $a_value, $datatype=null)
setBold(string $a_coords)
Set cell(s) to bold.
getColumnCoord(int $a_col)
Get column "name" from number.
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
language handling
txt(string $a_topic, string $a_default_lang_fallback_mod="")
gets the text for a given topic if the topic is not in the list, the topic itself with "-" will be re...
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
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...
static convertImage(string $a_from, string $a_to, string $a_target_format="", string $a_geometry="", string $a_background_color="")
convert image
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
global $DIC
Definition: feed.php:28
$mobs
Definition: imgupload.php:70
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...
$path
Definition: ltiservices.php:32
$res
Definition: ltiservices.php:69
$post
Definition: ltitoken.php:49
$index
Definition: metadata.php:145
$i
Definition: metadata.php:41
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples