ILIAS  release_8 Revision v8.24
class.assSingleChoice.php
Go to the documentation of this file.
1<?php
2
19require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20
35{
36 private bool $isSingleline = true;
37
45 public $answers;
46
56
64
79 public function __construct(
80 $title = "",
81 $comment = "",
82 $author = "",
83 $owner = -1,
84 $question = "",
86 ) {
88 $this->output_type = $output_type;
89 $this->answers = array();
90 $this->shuffle = 1;
91 $this->feedback_setting = 2;
92 }
93
100 public function isComplete(): bool
101 {
102 if (strlen($this->title) and ($this->author) and ($this->question) and (count($this->answers)) and ($this->getMaximumPoints() > 0)) {
103 foreach ($this->answers as $answer) {
104 if ((strlen($answer->getAnswertext()) == 0) && (strlen($answer->getImage()) == 0)) {
105 return false;
106 }
107 }
108 return true;
109 } else {
110 return false;
111 }
112 }
113
120 public function saveToDb($original_id = ""): void
121 {
123 global $DIC;
124 $ilDB = $DIC['ilDB'];
125
126 if ($original_id == '') {
127 $this->saveQuestionDataToDb();
128 } else {
130 }
131 // kann das weg?
132 $oldthumbsize = 0;
133 if ($this->isSingleline && ($this->getThumbSize())) {
134 // get old thumbnail size
135 $result = $ilDB->queryF(
136 "SELECT thumb_size FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
137 array("integer"),
138 array($this->getId())
139 );
140 if ($result->numRows() == 1) {
141 $data = $ilDB->fetchAssoc($result);
142 $oldthumbsize = $data['thumb_size'];
143 }
144 }
145
146
148
150
151 parent::saveToDb($original_id);
152 }
153
154 /*
155 * Rebuild the thumbnail images with a new thumbnail size
156 */
157 protected function rebuildThumbnails(): void
158 {
159 if ($this->isSingleline && ($this->getThumbSize())) {
160 foreach ($this->getAnswers() as $answer) {
161 if (strlen($answer->getImage())) {
162 $this->generateThumbForFile($this->getImagePath(), $answer->getImage());
163 }
164 }
165 }
166 }
167
168 public function getThumbPrefix(): string
169 {
170 return "thumb.";
171 }
172
173 protected function generateThumbForFile($path, $file): void
174 {
175 $filename = $path . $file;
176 if (@file_exists($filename)) {
177 $thumbpath = $path . $this->getThumbPrefix() . $file;
178 $path_info = @pathinfo($filename);
179 $ext = "";
180 switch (strtoupper($path_info['extension'])) {
181 case 'PNG':
182 $ext = 'PNG';
183 break;
184 case 'GIF':
185 $ext = 'GIF';
186 break;
187 default:
188 $ext = 'JPEG';
189 break;
190 }
191 ilShellUtil::convertImage($filename, $thumbpath, $ext, (string) $this->getThumbSize());
192 }
193 }
194
202 public function loadFromDb($question_id): void
203 {
204 global $DIC;
205 $ilDB = $DIC['ilDB'];
206
207 $result = $ilDB->queryF(
208 "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",
209 array("integer"),
210 array($question_id)
211 );
212 if ($result->numRows() == 1) {
213 $data = $ilDB->fetchAssoc($result);
214 $this->setId($question_id);
215 $this->setObjId($data["obj_fi"]);
216 $this->setTitle($data["title"] ?? '');
217 $this->setNrOfTries($data['nr_of_tries']);
218 $this->setComment($data["description"] ?? '');
219 $this->setOriginalId($data["original_id"]);
220 $this->setAuthor($data["author"]);
221 $this->setPoints($data["points"]);
222 $this->setOwner($data["owner"]);
223 $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"] ?? '', 1));
224 $shuffle = (is_null($data['shuffle'])) ? true : $data['shuffle'];
225 $this->setShuffle((bool) $shuffle);
226 if ($data['thumb_size'] !== null && $data['thumb_size'] >= $this->getMinimumThumbSize()) {
227 $this->setThumbSize($data['thumb_size']);
228 }
229 $this->isSingleline = $data['allow_images'] === null || $data['allow_images'] === '0';
230 $this->lastChange = $data['tstamp'];
231 $this->feedback_setting = $data['feedback_setting'];
232
233 try {
237 }
238
239 try {
240 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
242 }
243 }
244
245 $result = $ilDB->queryF(
246 "SELECT * FROM qpl_a_sc WHERE question_fi = %s ORDER BY aorder ASC",
247 array('integer'),
248 array($question_id)
249 );
250
251 if ($result->numRows() > 0) {
252 while ($data = $ilDB->fetchAssoc($result)) {
253 $imagefilename = $this->getImagePath() . $data["imagefile"];
254 if (!@file_exists($imagefilename)) {
255 $data["imagefile"] = "";
256 }
257
258 $data["answertext"] = ilRTE::_replaceMediaObjectImageSrc($data["answertext"] ?? '', 1);
259 $image = new ASS_AnswerBinaryStateImage(
260 $data["answertext"],
261 $data["points"],
262 $data["aorder"],
263 1,
264 $data["imagefile"],
265 $data["answer_id"]
266 );
267 $this->answers[] = $image;
268 }
269 }
270
271 parent::loadFromDb($question_id);
272 }
273
279 public function duplicate(bool $for_test = true, string $title = "", string $author = "", string $owner = "", $testObjId = null): int
280 {
281 if ($this->id <= 0) {
282 // The question has not been saved. It cannot be duplicated
283 return -1;
284 }
285 // duplicate the question in database
286 $this_id = $this->getId();
287 $thisObjId = $this->getObjId();
288
289 $clone = $this;
291 $clone->id = -1;
292
293 if ((int) $testObjId > 0) {
294 $clone->setObjId($testObjId);
295 }
296
297 if ($title) {
298 $clone->setTitle($title);
299 }
300
301 if ($author) {
302 $clone->setAuthor($author);
303 }
304 if ($owner) {
305 $clone->setOwner($owner);
306 }
307 if ($for_test) {
308 $clone->saveToDb($original_id);
309 } else {
310 $clone->saveToDb();
311 }
312
313 // copy question page content
314 $clone->copyPageOfQuestion($this_id);
315
316 // copy XHTML media objects
317 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
318 // duplicate the images
319 $clone->duplicateImages($this_id, $thisObjId);
320
321 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
322
323 return $clone->id;
324 }
325
331 public function copyObject($target_questionpool_id, $title = ""): int
332 {
333 if ($this->getId() <= 0) {
334 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
335 }
336 // duplicate the question in database
337 $clone = $this;
339 $clone->id = -1;
340 $source_questionpool_id = $this->getObjId();
341 $clone->setObjId($target_questionpool_id);
342 if ($title) {
343 $clone->setTitle($title);
344 }
345 $clone->saveToDb();
346 // copy question page content
347 $clone->copyPageOfQuestion($original_id);
348 // copy XHTML media objects
349 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
350 // duplicate the image
351 $clone->copyImages($original_id, $source_questionpool_id);
352
353 $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
354
355 return $clone->id;
356 }
357
358 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = ""): int
359 {
360 if ($this->getId() <= 0) {
361 throw new RuntimeException('The question has not been saved. It cannot be duplicated');
362 }
363
364 $sourceQuestionId = $this->id;
365 $sourceParentId = $this->getObjId();
366
367 // duplicate the question in database
368 $clone = $this;
369 $clone->id = -1;
370
371 $clone->setObjId($targetParentId);
372
373 if ($targetQuestionTitle) {
374 $clone->setTitle($targetQuestionTitle);
375 }
376
377 $clone->saveToDb();
378 // copy question page content
379 $clone->copyPageOfQuestion($sourceQuestionId);
380 // copy XHTML media objects
381 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
382 // duplicate the image
383 $clone->copyImages($sourceQuestionId, $sourceParentId);
384
385 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
386
387 return $clone->id;
388 }
389
397 public function getOutputType(): int
398 {
399 return $this->output_type;
400 }
401
409 public function setOutputType($output_type = OUTPUT_ORDER): void
410 {
411 $this->output_type = $output_type;
412 }
413
427 public function addAnswer(
428 $answertext = "",
429 $points = 0.0,
430 $order = 0,
431 $answerimage = "",
432 $answer_id = -1
433 ): void {
434 $answertext = $this->getHtmlQuestionContentPurifier()->purify($answertext);
435 if (array_key_exists($order, $this->answers)) {
436 // insert answer
437 $answer = new ASS_AnswerBinaryStateImage($answertext, $points, $order, 1, $answerimage, $answer_id);
438 $newchoices = array();
439 for ($i = 0; $i < $order; $i++) {
440 $newchoices[] = $this->answers[$i];
441 }
442 $newchoices[] = $answer;
443 for ($i = $order, $iMax = count($this->answers); $i < $iMax; $i++) {
444 $changed = $this->answers[$i];
445 $changed->setOrder($i + 1);
446 $newchoices[] = $changed;
447 }
448 $this->answers = $newchoices;
449 } else {
450 $answer = new ASS_AnswerBinaryStateImage(
451 $answertext,
452 $points,
453 count($this->answers),
454 1,
455 $answerimage,
456 $answer_id
457 );
458 $this->answers[] = $answer;
459 }
460 }
461
469 public function getAnswerCount(): int
470 {
471 return count($this->answers);
472 }
473
483 public function getAnswer($index = 0): ?object
484 {
485 if ($index < 0) {
486 return null;
487 }
488 if (count($this->answers) < 1) {
489 return null;
490 }
491 if ($index >= count($this->answers)) {
492 return null;
493 }
494
495 return $this->answers[$index];
496 }
497
506 public function deleteAnswer($index = 0): void
507 {
508 if ($index < 0) {
509 return;
510 }
511 if (count($this->answers) < 1) {
512 return;
513 }
514 if ($index >= count($this->answers)) {
515 return;
516 }
517 $answer = $this->answers[$index];
518 if (strlen($answer->getImage())) {
519 $this->deleteImage($answer->getImage());
520 }
521 unset($this->answers[$index]);
522 $this->answers = array_values($this->answers);
523 for ($i = 0, $iMax = count($this->answers); $i < $iMax; $i++) {
524 if ($this->answers[$i]->getOrder() > $index) {
525 $this->answers[$i]->setOrder($i);
526 }
527 }
528 }
529
536 public function flushAnswers(): void
537 {
538 $this->answers = array();
539 }
540
547 public function getMaximumPoints(): float
548 {
549 $points = 0;
550 foreach ($this->answers as $key => $value) {
551 if ($value->getPoints() > $points) {
552 $points = $value->getPoints();
553 }
554 }
555 return $points;
556 }
557
568 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
569 {
570 if ($returndetails) {
571 throw new ilTestException('return details not implemented for ' . __METHOD__);
572 }
573
574 global $DIC;
575 $ilDB = $DIC['ilDB'];
576
577 $found_values = array();
578 if (is_null($pass)) {
579 $pass = $this->getSolutionMaxPass($active_id);
580 }
581 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorizedSolution);
582 while ($data = $ilDB->fetchAssoc($result)) {
583 if (strcmp($data["value1"], "") != 0) {
584 array_push($found_values, $data["value1"]);
585 }
586 }
587 $points = 0;
588 foreach ($this->answers as $key => $answer) {
589 if (count($found_values) > 0) {
590 if (in_array($key, $found_values)) {
591 $points += $answer->getPoints();
592 }
593 }
594 }
595
596 return $points;
597 }
598
600 {
601 $participantSolution = $previewSession->getParticipantsSolution();
602
603 $points = 0;
604
605 foreach ($this->answers as $key => $answer) {
606 if (is_numeric($participantSolution) && $key == $participantSolution) {
607 $points = $answer->getPoints();
608 }
609 }
610
611 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $points);
612
613 return $this->ensureNonNegativePoints($reachedPoints);
614 }
615
623 public function saveWorkingData($active_id, $pass = null, $authorized = true): bool
624 {
625 global $DIC;
626 $ilDB = $DIC['ilDB'];
627 $ilUser = $DIC['ilUser'];
628
629 if (is_null($pass)) {
630 $pass = ilObjTest::_getPass($active_id);
631 }
632
633 $entered_values = 0;
634
635 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $ilDB, $active_id, $pass, $authorized) {
636 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
637
638 $update = -1;
639 if ($ilDB->numRows($result)) {
640 $row = $ilDB->fetchAssoc($result);
641 $update = $row["solution_id"];
642 }
643
644 $multiple_choice_result = $this->http->wrapper()->post()->has('multiple_choice_result') ?
645 $this->http->wrapper()->post()->retrieve('multiple_choice_result', $this->refinery->kindlyTo()->string()) :
646 '';
647
648 if ($update != -1) {
649 if ($multiple_choice_result !== '') {
650 $this->updateCurrentSolution($update, $multiple_choice_result, null, $authorized);
651 $entered_values++;
652 } else {
653 $this->removeSolutionRecordById($update);
654 }
655 } else {
656 if ($multiple_choice_result !== '') {
657 $this->saveCurrentSolution($active_id, $pass, $multiple_choice_result, null, $authorized);
658 $entered_values++;
659 }
660 }
661 });
662
663 if ($entered_values) {
665 assQuestion::logAction($this->lng->txtlng(
666 "assessment",
667 "log_user_entered_values",
669 ), $active_id, $this->getId());
670 }
671 } else {
673 assQuestion::logAction($this->lng->txtlng(
674 "assessment",
675 "log_user_not_entered_values",
677 ), $active_id, $this->getId());
678 }
679 }
680
681 return true;
682 }
683
684 protected function savePreviewData(ilAssQuestionPreviewSession $previewSession): void
685 {
686 $mc_result_key = 'multiple_choice_result' . $this->getId() . 'ID';
687 if (
688 $this->http->wrapper()->post()->has($mc_result_key) &&
689 ($mc_result = $this->http->wrapper()->post()->retrieve($mc_result_key, $this->refinery->kindlyTo()->string())) !== ''
690 ) {
691 $previewSession->setParticipantsSolution($mc_result);
692 } else {
693 $previewSession->setParticipantsSolution(null);
694 }
695 }
696
697 public function saveAdditionalQuestionDataToDb()
698 {
700 global $DIC;
701 $ilDB = $DIC['ilDB'];
702
703 // save additional data
704 $ilDB->manipulateF(
705 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
706 array( "integer" ),
707 array( $this->getId() )
708 );
709
710 $ilDB->manipulateF(
711 "INSERT INTO " . $this->getAdditionalTableName(
712 ) . " (question_fi, shuffle, allow_images, thumb_size) VALUES (%s, %s, %s, %s)",
713 array( "integer", "text", "text", "integer" ),
714 array(
715 $this->getId(),
716 $this->getShuffle(),
717 ($this->isSingleline) ? "0" : "1",
718 (strlen($this->getThumbSize()) == 0) ? null : $this->getThumbSize()
719 )
720 );
721 }
722
728 public function saveAnswerSpecificDataToDb()
729 {
731 global $DIC;
732 $ilDB = $DIC['ilDB'];
733
734 if (!$this->isSingleline) {
735 ilFileUtils::delDir($this->getImagePath());
736 }
737 // Get all feedback entries
738 $result = $ilDB->queryF(
739 "SELECT * FROM qpl_fb_specific WHERE question_fi = %s",
740 ['integer'],
741 [$this->getId()]
742 );
743 $db_feedback = $ilDB->fetchAll($result);
744
745 // Check if feedback exists and the regular editor is used and not the page editor
746 if (sizeof($db_feedback) >= 1 && $this->getAdditionalContentEditingMode() == 'default') {
747 // Get all existing answer data for question
748 $result = $ilDB->queryF(
749 "SELECT answer_id, aorder FROM qpl_a_sc WHERE question_fi = %s",
750 ['integer'],
751 [$this->getId()]
752 );
753 $db_answers = $ilDB->fetchAll($result);
754
755 // Collect old and new order entries by ids and order to calculate a diff/intersection and remove/update feedback
756 $post_answer_order_for_id = [];
757 foreach ($this->answers as $answer) {
758 // Only the first appearance of an id is used
759 if ($answer->getId() !== null && !in_array($answer->getId(), array_keys($post_answer_order_for_id))) {
760 // -1 is happening while import and also if a new multi line answer is generated
761 if ($answer->getId() == -1) {
762 continue;
763 }
764 $post_answer_order_for_id[$answer->getId()] = $answer->getOrder();
765 }
766 }
767
768 // If there is no usable ids from post, it's better to not touch the feedback
769 // This is useful since the import is also using this function or the first creation of a new question in general
770 if (sizeof($post_answer_order_for_id) >= 1) {
771 $db_answer_order_for_id = [];
772 $db_answer_id_for_order = [];
773 foreach ($db_answers as $db_answer) {
774 $db_answer_order_for_id[intval($db_answer['answer_id'])] = intval($db_answer['aorder']);
775 $db_answer_id_for_order[intval($db_answer['aorder'])] = intval($db_answer['answer_id']);
776 }
777
778 // Handle feedback
779 // the diff between the already existing answer ids from the Database and the answer ids from post
780 // feedback related to the answer ids should be deleted or in our case not recreated.
781 $db_answer_ids = array_keys($db_answer_order_for_id);
782 $post_answer_ids = array_keys($post_answer_order_for_id);
783 $diff_db_post_answer_ids = array_diff($db_answer_ids, $post_answer_ids);
784 $unused_answer_ids = array_keys($diff_db_post_answer_ids);
785
786 // Delete all feedback in the database
787 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($this->getId(), false);
788 // Recreate feedback
789 foreach ($db_feedback as $feedback_option) {
790 // skip feedback which answer is deleted
791 if (in_array(intval($feedback_option['answer']), $unused_answer_ids)) {
792 continue;
793 }
794
795 // Reorder feedback
796 $feedback_order_db = intval($feedback_option['answer']);
797 $db_answer_id = $db_answer_id_for_order[$feedback_order_db];
798 // This cuts feedback that currently would have no corresponding answer
799 // This case can happen while copying "broken" questions
800 // Or when saving a question with less answers than feedback
801 if (is_null($db_answer_id) || $db_answer_id < 0) {
802 continue;
803 }
804 $feedback_order_post = $post_answer_order_for_id[$db_answer_id];
805 $feedback_option['answer'] = $feedback_order_post;
806
807 // Recreate remaining feedback in database
808 $next_id = $ilDB->nextId('qpl_fb_specific');
809 $ilDB->manipulateF(
810 "INSERT INTO qpl_fb_specific (feedback_id, question_fi, answer, tstamp, feedback, question)
811 VALUES (%s, %s, %s, %s, %s, %s)",
812 ['integer', 'integer', 'integer', 'integer', 'text', 'integer'],
813 [
814 $next_id,
815 $feedback_option['question_fi'],
816 $feedback_option['answer'],
817 time(),
818 $feedback_option['feedback'],
819 $feedback_option['question']
820 ]
821 );
822 }
823 }
824 }
825
826 // Delete all entries in qpl_a_sc for question
827 $ilDB->manipulateF(
828 "DELETE FROM qpl_a_sc WHERE question_fi = %s",
829 ['integer'],
830 [$this->getId()]
831 );
832
833 // Recreate answers one by one
834 foreach ($this->answers as $key => $value) {
836 $answer_obj = $this->answers[$key];
837 $next_id = $ilDB->nextId('qpl_a_sc');
838 $ilDB->manipulateF(
839 "INSERT INTO qpl_a_sc (answer_id, question_fi, answertext, points, aorder, imagefile, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
840 ['integer', 'integer', 'text', 'float', 'integer', 'text', 'integer'],
841 [
842 $next_id,
843 $this->getId(),
844 ilRTE::_replaceMediaObjectImageSrc($answer_obj->getAnswertext(), 0),
845 $answer_obj->getPoints(),
846 $answer_obj->getOrder(),
847 $answer_obj->getImage(),
848 time()
849 ]
850 );
851 }
852 $this->rebuildThumbnails();
853 }
854
861 public function getQuestionType(): string
862 {
863 return "assSingleChoice";
864 }
865
872 public function getAdditionalTableName(): string
873 {
874 return "qpl_qst_sc";
875 }
876
883 public function getAnswerTableName(): string
884 {
885 return "qpl_a_sc";
886 }
887
896 public function setImageFile($image_filename, $image_tempfilename = ""): int
897 {
898 $result = 0;
899 if (!empty($image_tempfilename)) {
900 $image_filename = str_replace(" ", "_", $image_filename);
901 $imagepath = $this->getImagePath();
902 if (!file_exists($imagepath)) {
903 ilFileUtils::makeDirParents($imagepath);
904 }
905 //if (!move_uploaded_file($image_tempfilename, $imagepath . $image_filename))
906 if (!ilFileUtils::moveUploadedFile($image_tempfilename, $image_filename, $imagepath . $image_filename)) {
907 $result = 2;
908 } else {
909 $mimetype = ilObjMediaObject::getMimeType($imagepath . $image_filename);
910 if (!preg_match("/^image/", $mimetype)) {
911 unlink($imagepath . $image_filename);
912 $result = 1;
913 } else {
914 // create thumbnail file
915 if ($this->isSingleline && ($this->getThumbSize())) {
916 $this->generateThumbForFile($imagepath, $image_filename);
917 }
918 }
919 }
920 }
921 return $result;
922 }
923
930 public function deleteImage($image_filename): void
931 {
932 $imagepath = $this->getImagePath();
933 @unlink($imagepath . $image_filename);
934 $thumbpath = $imagepath . $this->getThumbPrefix() . $image_filename;
935 @unlink($thumbpath);
936 }
937
938 public function duplicateImages($question_id, $objectId = null): void
939 {
940 global $DIC;
941 $ilLog = $DIC['ilLog'];
942 $imagepath = $this->getImagePath();
943 $imagepath_original = str_replace("/$this->id/images", "/$question_id/images", $imagepath);
944
945 if ((int) $objectId > 0) {
946 $imagepath_original = str_replace("/$this->obj_id/", "/$objectId/", $imagepath_original);
947 }
948
949 foreach ($this->answers as $answer) {
950 $filename = $answer->getImage();
951 if (strlen($filename)) {
952 if (!file_exists($imagepath)) {
953 ilFileUtils::makeDirParents($imagepath);
954 }
955 if (!@copy($imagepath_original . $filename, $imagepath . $filename)) {
956 $ilLog->write("image could not be duplicated!!!!", $ilLog->ERROR);
957 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
958 }
959 if (@file_exists($imagepath_original . $this->getThumbPrefix() . $filename)) {
960 if (!@copy($imagepath_original . $this->getThumbPrefix() . $filename, $imagepath . $this->getThumbPrefix() . $filename)) {
961 $ilLog->write("image thumbnail could not be duplicated!!!!", $ilLog->ERROR);
962 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
963 }
964 }
965 }
966 }
967 }
968
969 public function copyImages($question_id, $source_questionpool): void
970 {
972 global $DIC;
973 $ilLog = $DIC['ilLog'];
974
975 $imagepath = $this->getImagePath();
976 $imagepath_original = str_replace("/$this->id/images", "/$question_id/images", $imagepath);
977 $imagepath_original = str_replace("/$this->obj_id/", "/$source_questionpool/", $imagepath_original);
978 foreach ($this->answers as $answer) {
979 $filename = $answer->getImage();
980 if (strlen($filename)) {
981 if (!file_exists($imagepath)) {
982 ilFileUtils::makeDirParents($imagepath);
983 }
984
985 if (file_exists($imagepath_original . $filename)) {
986 if (!copy($imagepath_original . $filename, $imagepath . $filename)) {
987 $ilLog->warning(sprintf(
988 "Could not clone source image '%s' to '%s' (srcQuestionId: %s|tgtQuestionId: %s|srcParentObjId: %s|tgtParentObjId: %s)",
989 $imagepath_original . $filename,
990 $imagepath . $filename,
991 $question_id,
992 $this->id,
993 $source_questionpool,
994 $this->obj_id
995 ));
996 }
997 }
998
999 if (file_exists($imagepath_original . $this->getThumbPrefix() . $filename)) {
1000 if (!copy($imagepath_original . $this->getThumbPrefix() . $filename, $imagepath . $this->getThumbPrefix() . $filename)) {
1001 $ilLog->warning(sprintf(
1002 "Could not clone thumbnail source image '%s' to '%s' (srcQuestionId: %s|tgtQuestionId: %s|srcParentObjId: %s|tgtParentObjId: %s)",
1003 $imagepath_original . $this->getThumbPrefix() . $filename,
1004 $imagepath . $this->getThumbPrefix() . $filename,
1005 $question_id,
1006 $this->id,
1007 $source_questionpool,
1008 $this->obj_id
1009 ));
1010 }
1011 }
1012 }
1013 }
1014 }
1015
1019 protected function syncImages(): void
1020 {
1021 global $DIC;
1022 $ilLog = $DIC['ilLog'];
1023 $question_id = $this->getOriginalId();
1024 $imagepath = $this->getImagePath();
1025 $imagepath_original = str_replace("/$this->id/images", "/$question_id/images", $imagepath);
1026 ilFileUtils::delDir($imagepath_original);
1027 foreach ($this->answers as $answer) {
1028 $filename = $answer->getImage();
1029 if (strlen($filename)) {
1030 if (@file_exists($imagepath . $filename)) {
1031 if (!file_exists($imagepath)) {
1032 ilFileUtils::makeDirParents($imagepath);
1033 }
1034 if (!file_exists($imagepath_original)) {
1035 ilFileUtils::makeDirParents($imagepath_original);
1036 }
1037 if (!@copy($imagepath . $filename, $imagepath_original . $filename)) {
1038 $ilLog->write("image could not be duplicated!!!!", $ilLog->ERROR);
1039 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
1040 }
1041 }
1042 if (@file_exists($imagepath . $this->getThumbPrefix() . $filename)) {
1043 if (!@copy($imagepath . $this->getThumbPrefix() . $filename, $imagepath_original . $this->getThumbPrefix() . $filename)) {
1044 $ilLog->write("image thumbnail could not be duplicated!!!!", $ilLog->ERROR);
1045 $ilLog->write("object: " . print_r($this, true), $ilLog->ERROR);
1046 }
1047 }
1048 }
1049 }
1050 }
1051
1056 public function getRTETextWithMediaObjects(): string
1057 {
1058 $text = parent::getRTETextWithMediaObjects();
1059 foreach ($this->answers as $index => $answer) {
1060 $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), 0, $index);
1061 $answer_obj = $this->answers[$index];
1062 $text .= $answer_obj->getAnswertext();
1063 }
1064 return $text;
1065 }
1066
1070 public function &getAnswers(): array
1071 {
1072 return $this->answers;
1073 }
1074
1078 public function setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass): int
1079 {
1080 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1081
1082 $solution = $this->getSolutionValues($active_id, $pass);
1083 $i = 1;
1084 foreach ($this->getAnswers() as $id => $answer) {
1085 $worksheet->setCell($startrow + $i, 0, $answer->getAnswertext());
1086 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1087 if (
1088 count($solution) > 0 &&
1089 isset($solution[0]) &&
1090 is_array($solution[0]) &&
1091 strlen($solution[0]['value1']) > 0 && $id == $solution[0]['value1']
1092 ) {
1093 $worksheet->setCell($startrow + $i, 2, 1);
1094 } else {
1095 $worksheet->setCell($startrow + $i, 2, 0);
1096 }
1097 $i++;
1098 }
1099
1100 return $startrow + $i + 1;
1101 }
1102
1107 {
1108 foreach ($this->getAnswers() as $answer) {
1109 /* @var ASS_AnswerBinaryStateImage $answer */
1110 $answer->setAnswertext($migrator->migrateToLmContent($answer->getAnswertext()));
1111 }
1112 }
1113
1117 public function toJSON(): string
1118 {
1119 $result = array();
1120 $result['id'] = $this->getId();
1121 $result['type'] = (string) $this->getQuestionType();
1122 $result['title'] = $this->getTitleForHTMLOutput();
1123 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1124 $result['nr_of_tries'] = $this->getNrOfTries();
1125 $result['shuffle'] = $this->getShuffle();
1126
1127 $result['feedback'] = array(
1128 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1129 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1130 );
1131
1132 $answers = array();
1133 $has_image = false;
1134 foreach ($this->getAnswers() as $key => $answer_obj) {
1135 if ((string) $answer_obj->getImage()) {
1136 $has_image = true;
1137 }
1138 array_push($answers, array(
1139 "answertext" => $this->formatSAQuestion($answer_obj->getAnswertext()),
1140 'html_id' => $this->getId() . '_' . $key,
1141 "points" => (float) $answer_obj->getPoints(),
1142 "order" => (int) $answer_obj->getOrder(),
1143 "image" => (string) $answer_obj->getImage(),
1144 "feedback" => $this->formatSAQuestion(
1145 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
1146 )
1147 ));
1148 }
1149 $result['answers'] = $answers;
1150 if ($has_image) {
1151 $result['path'] = $this->getImagePathWeb();
1152 $result['thumb'] = $this->getThumbSize();
1153 }
1154
1155 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1156 $result['mobs'] = $mobs;
1157
1158 return json_encode($result);
1159 }
1160
1161 public function removeAnswerImage($index): void
1162 {
1163 $answer = $this->answers[$index];
1164 if (is_object($answer)) {
1165 $this->deleteImage($answer->getImage());
1166 $answer->setImage('');
1167 }
1168 }
1169
1171 {
1172 global $DIC;
1173 $ilUser = $DIC['ilUser'];
1174
1175 $multilineAnswerSetting = $ilUser->getPref("tst_multiline_answers");
1176 if ($multilineAnswerSetting != 1) {
1177 $multilineAnswerSetting = 0;
1178 }
1179 return $multilineAnswerSetting;
1180 }
1181
1182 public function setMultilineAnswerSetting($a_setting = 0): void
1183 {
1184 global $DIC;
1185 $ilUser = $DIC['ilUser'];
1186 $ilUser->writePref("tst_multiline_answers", (string) $a_setting);
1187 }
1188
1198 public function setSpecificFeedbackSetting($a_feedback_setting): void
1199 {
1200 $this->feedback_setting = $a_feedback_setting;
1201 }
1202
1213 {
1214 if ($this->feedback_setting) {
1215 return $this->feedback_setting;
1216 } else {
1217 return 1;
1218 }
1219 }
1220
1222 {
1223 return 'feedback_correct_sc_mc';
1224 }
1225
1236 public function isAnswered(int $active_id, int $pass): bool
1237 {
1238 $numExistingSolutionRecords = assQuestion::getNumExistingSolutionRecords($active_id, $pass, $this->getId());
1239
1240 return $numExistingSolutionRecords > 0;
1241 }
1242
1253 public static function isObligationPossible(int $questionId): bool
1254 {
1255 return true;
1256 }
1257
1266 public function getOperators($expression): array
1267 {
1269 }
1270
1275 public function getExpressionTypes(): array
1276 {
1277 return array(
1281 );
1282 }
1283
1292 public function getUserQuestionResult($active_id, $pass): ilUserQuestionResult
1293 {
1295 global $DIC;
1296 $ilDB = $DIC['ilDB'];
1297 $result = new ilUserQuestionResult($this, $active_id, $pass);
1298
1299 $maxStep = $this->lookupMaxStep($active_id, $pass);
1300
1301 if ($maxStep !== null) {
1302 $data = $ilDB->queryF(
1303 "SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
1304 array("integer", "integer", "integer","integer"),
1305 array($active_id, $pass, $this->getId(), $maxStep)
1306 );
1307 } else {
1308 $data = $ilDB->queryF(
1309 "SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
1310 array("integer", "integer", "integer"),
1311 array($active_id, $pass, $this->getId())
1312 );
1313 }
1314
1315 $row = $ilDB->fetchAssoc($data);
1316
1317 if ($row != null) {
1318 ++$row["value1"];
1319 $result->addKeyValue($row["value1"], $row["value1"]);
1320 }
1321
1322 $points = $this->calculateReachedPoints($active_id, $pass);
1323 $max_points = $this->getMaximumPoints();
1324
1325 $result->setReachedPercentage(($points / $max_points) * 100);
1326
1327 return $result;
1328 }
1329
1336 public function getAvailableAnswerOptions($index = null)
1337 {
1338 if ($index !== null) {
1339 return $this->getAnswer($index);
1340 } else {
1341 return $this->getAnswers();
1342 }
1343 }
1344
1348 protected function afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId): void
1349 {
1350 parent::afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId);
1351
1352 $origImagePath = $this->buildImagePath($origQuestionId, $origParentObjId);
1353 $dupImagePath = $this->buildImagePath($dupQuestionId, $dupParentObjId);
1354
1355 ilFileUtils::delDir($origImagePath);
1356 if (is_dir($dupImagePath)) {
1357 ilFileUtils::makeDirParents($origImagePath);
1358 ilFileUtils::rCopy($dupImagePath, $origImagePath);
1359 }
1360 }
1361
1362 public function isSingleline(): bool
1363 {
1364 return $this->isSingleline;
1365 }
1366
1367 public function setIsSingleline(bool $isSingleline): void
1368 {
1369 $this->isSingleline = $isSingleline;
1370 }
1371
1372 public function getFeedbackSetting(): int
1373 {
1374 return $this->feedback_setting;
1375 }
1376
1377 public function setFeedbackSetting(int $feedback_setting): void
1378 {
1379 $this->feedback_setting = $feedback_setting;
1380 }
1381}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$filename
Definition: buildRTE.php:78
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
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)
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.
static _getOriginalId(int $question_id)
saveQuestionDataToDb(int $original_id=-1)
setAuthor(string $author="")
setThumbSize(int $a_size)
bool $shuffle
Indicates whether the answers will be shuffled or not.
setComment(string $comment="")
setObjId(int $obj_id=0)
setOwner(int $owner=-1)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
static getNumExistingSolutionRecords(int $activeId, int $pass, int $questionId)
setTitle(string $title="")
setPoints(float $points)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
isAnswered(int $active_id, int $pass)
returns boolean wether the question is answered during test pass or not
loadFromDb($question_id)
Loads a assSingleChoice object from a database.
deleteImage($image_filename)
Deletes an image file.
setOutputType($output_type=OUTPUT_ORDER)
Sets the output type of the assSingleChoice object.
addAnswer( $answertext="", $points=0.0, $order=0, $answerimage="", $answer_id=-1)
Adds a possible answer for a single choice question.
getQuestionType()
Returns the question type of the question.
getAnswerCount()
Returns the number of answers.
toJSON()
Returns a JSON representation of the question.
setFeedbackSetting(int $feedback_setting)
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
generateThumbForFile($path, $file)
syncImages()
Sync images of a MC question on synchronisation with the original question.
setExportDetailsXLS(ilAssExcelFormatHelper $worksheet, int $startrow, int $active_id, int $pass)
{}
duplicate(bool $for_test=true, string $title="", string $author="", string $owner="", $testObjId=null)
Duplicates an assSingleChoiceQuestion.
setSpecificFeedbackSetting($a_feedback_setting)
Sets the feedback settings in effect for the question.
getOutputType()
Gets the single choice output type which is either OUTPUT_ORDER (=0) or OUTPUT_RANDOM (=1).
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
static isObligationPossible(int $questionId)
returns boolean wether it is possible to set this question type as obligatory or not considering the ...
getSpecificFeedbackSetting()
Gets the current feedback settings in effect for the question.
getOperators($expression)
Get all available operations for a specific question.
setMultilineAnswerSetting($a_setting=0)
getExpressionTypes()
Get all available expression types for a specific question.
setIsSingleline(bool $isSingleline)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
afterSyncWithOriginal($origQuestionId, $dupQuestionId, $origParentObjId, $dupParentObjId)
{}
& getAnswers()
Returns a reference to the answers array.
isComplete()
Returns true, if a single choice question is complete for use.
deleteAnswer($index=0)
Deletes an answer with a given index.
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
savePreviewData(ilAssQuestionPreviewSession $previewSession)
setImageFile($image_filename, $image_tempfilename="")
Sets the image file and uploads the image to the object's image directory.
copyObject($target_questionpool_id, $title="")
Copies an assSingleChoice object.
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
__construct( $title="", $comment="", $author="", $owner=-1, $question="", $output_type=OUTPUT_ORDER)
assSingleChoice constructor
flushAnswers()
Deletes all answers.
getAnswer($index=0)
Returns an answer with a given index.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
duplicateImages($question_id, $objectId=null)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
getAnswerTableName()
Returns the name of the answer table in the database.
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
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 _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...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
global $DIC
Definition: feed.php:28
$update
Definition: imgupload.php:92
$mobs
Definition: imgupload.php:70
$ilUser
Definition: imgupload.php:34
const OUTPUT_ORDER
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...
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
$path
Definition: ltiservices.php:32
$index
Definition: metadata.php:145
$i
Definition: metadata.php:41
static http()
Fetches the global http state from ILIAS.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193