ILIAS  release_7 Revision v7.30-3-g800a261c036
class.assClozeTest.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once './Modules/TestQuestionPool/classes/class.assQuestion.php';
5require_once './Modules/Test/classes/inc.AssessmentConstants.php';
6require_once './Modules/TestQuestionPool/classes/class.assClozeGapCombination.php';
7require_once './Modules/TestQuestionPool/interfaces/interface.ilObjQuestionScoringAdjustable.php';
8require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php';
9require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php';
10require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
11require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php';
12
25{
33 public $gaps;
34
43
44
46
54 public $start_tag;
55
63 public $end_tag;
64
76
87
94
96
101
103
117 public function __construct(
118 $title = "",
119 $comment = "",
120 $author = "",
121 $owner = -1,
122 $question = ""
123 ) {
125 $this->start_tag = "[gap]";
126 $this->end_tag = "[/gap]";
127 $this->gaps = array();
128 $this->setQuestion($question); // @TODO: Should this be $question?? See setter for why this is not trivial.
129 $this->fixedTextLength = "";
130 $this->identical_scoring = 1;
131 $this->gap_combinations_exists = false;
132 $this->gap_combinations = array();
133 }
134
140 public function isComplete()
141 {
142 if (strlen($this->getTitle())
143 && $this->getAuthor()
144 && $this->getClozeText()
145 && count($this->getGaps())
146 && $this->getMaximumPoints() > 0) {
147 return true;
148 }
149 return false;
150 }
151
159 public function cleanQuestiontext($text)
160 {
161 // fau: fixGapReplace - mask dollars for replacement
162 $text = str_replace('$', 'GAPMASKEDDOLLAR', $text);
163 $text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
164 $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
165 $text = str_replace("</gap>", "[/gap]", $text);
166 $text = str_replace('GAPMASKEDDOLLAR', '$', $text);
167 // fau.
168 return $text;
169 }
170
171 // fau: fixGapReplace - add function replaceFirstGap()
178 public function replaceFirstGap($gaptext, $content)
179 {
180 $content = str_replace('$', 'GAPMASKEDDOLLAR', $content);
181 $output = preg_replace("/\[gap\].*?\[\/gap\]/", $content, $gaptext, 1);
182 $output = str_replace('GAPMASKEDDOLLAR', '$', $output);
183
184 return $output;
185 }
186 // fau.
193 public function loadFromDb($question_id)
194 {
195 global $DIC;
196 $ilDB = $DIC['ilDB'];
197 $result = $ilDB->queryF(
198 "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",
199 array("integer"),
200 array($question_id)
201 );
202 if ($result->numRows() == 1) {
203 $data = $ilDB->fetchAssoc($result);
204 $this->setId($question_id);
205 $this->setNrOfTries($data['nr_of_tries']);
206 $this->setObjId($data["obj_fi"]);
207 $this->setTitle($data["title"]);
208 $this->setComment($data["description"]);
209 $this->setOriginalId($data["original_id"]);
210 $this->setAuthor($data["author"]);
211 $this->setPoints($data["points"]);
212 $this->setOwner($data["owner"]);
213 $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
214 $this->setClozeText($data['cloze_text']);
215 $this->setFixedTextLength($data["fixed_textlen"]);
216 $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
217 $this->setFeedbackMode($data['feedback_mode'] === null ? ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION : $data['feedback_mode']);
218
219 try {
220 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
223 }
224
225 // replacement of old syntax with new syntax
226 include_once("./Services/RTE/classes/class.ilRTE.php");
227 $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
228 $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
229 $this->setTextgapRating($data["textgap_rating"]);
230 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
231
232 try {
233 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
235 }
236
237 // open the cloze gaps with all answers
238 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
239 include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
240 $result = $ilDB->queryF(
241 "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
242 array("integer"),
243 array($question_id)
244 );
245 if ($result->numRows() > 0) {
246 $this->gaps = array();
247 while ($data = $ilDB->fetchAssoc($result)) {
248 switch ($data["cloze_type"]) {
249 case CLOZE_TEXT:
250 if (!array_key_exists($data["gap_id"], $this->gaps)) {
251 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
252 }
253 $answer = new assAnswerCloze(
254 $data["answertext"],
255 $data["points"],
256 $data["aorder"]
257 );
258 $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
259
260 $this->gaps[$data["gap_id"]]->addItem($answer);
261 break;
262 case CLOZE_SELECT:
263 if (!array_key_exists($data["gap_id"], $this->gaps)) {
264 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
265 $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
266 }
267 $answer = new assAnswerCloze(
268 $data["answertext"],
269 $data["points"],
270 $data["aorder"]
271 );
272 $this->gaps[$data["gap_id"]]->addItem($answer);
273 break;
274 case CLOZE_NUMERIC:
275 if (!array_key_exists($data["gap_id"], $this->gaps)) {
276 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
277 }
278 $answer = new assAnswerCloze(
279 $data["answertext"],
280 $data["points"],
281 $data["aorder"]
282 );
283 $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
284 $answer->setLowerBound($data["lowerlimit"]);
285 $answer->setUpperBound($data["upperlimit"]);
286 $this->gaps[$data["gap_id"]]->addItem($answer);
287 break;
288 }
289 }
290 }
291 }
292 $assClozeGapCombinationObj = new assClozeGapCombination();
293 $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
294 if (count($check_for_gap_combinations) != 0) {
295 $this->setGapCombinationsExists(true);
296 $this->setGapCombinations($check_for_gap_combinations);
297 }
298 parent::loadFromDb($question_id);
299 }
300
301 #region Save question to db
302
312 public function saveToDb($original_id = "")
313 {
317
318 parent::saveToDb($original_id);
319 }
320
325 {
326 global $DIC;
327 $ilDB = $DIC['ilDB'];
328
329 $ilDB->manipulateF(
330 "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
331 array( "integer" ),
332 array( $this->getId() )
333 );
334
335 foreach ($this->gaps as $key => $gap) {
336 $this->saveClozeGapItemsToDb($gap, $key);
337 }
338 }
339
346 {
347 global $DIC; /* @var ILIAS\DI\Container $DIC */
348
349
350 $DIC->database()->manipulateF(
351 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
352 array( "integer" ),
353 array( $this->getId() )
354 );
355
356 $DIC->database()->insert($this->getAdditionalTableName(), array(
357 'question_fi' => array('integer', $this->getId()),
358 'textgap_rating' => array('text', $this->getTextgapRating()),
359 'identical_scoring' => array('text', $this->getIdenticalScoring()),
360 'fixed_textlen' => array('integer', $this->getFixedTextLength() ? $this->getFixedTextLength() : null),
361 'cloze_text' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0)),
362 'feedback_mode' => array('text', $this->getFeedbackMode())
363 ));
364 }
365
372 protected function saveClozeGapItemsToDb($gap, $key)
373 {
374 global $DIC;
375 $ilDB = $DIC['ilDB'];
376 foreach ($gap->getItems($this->getShuffler()) as $item) {
377 $query = "";
378 $next_id = $ilDB->nextId('qpl_a_cloze');
379 switch ($gap->getType()) {
380 case CLOZE_TEXT:
381 $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
382 break;
383 case CLOZE_SELECT:
384 $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
385 break;
386 case CLOZE_NUMERIC:
387 $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
388 break;
389 }
390 }
391 }
392
401 protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
402 {
403 global $DIC;
404 $ilDB = $DIC['ilDB'];
405 $ilDB->manipulateF(
406 "INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, gap_size) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
407 array(
408 "integer",
409 "integer",
410 "integer",
411 "text",
412 "float",
413 "integer",
414 "text",
415 "integer"
416 ),
417 array(
418 $next_id,
419 $this->getId(),
420 $key,
421 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
422 $item->getPoints(),
423 $item->getOrder(),
424 $gap->getType(),
425 (int) $gap->getGapSize()
426 )
427 );
428 }
429
438 protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
439 {
440 global $DIC;
441 $ilDB = $DIC['ilDB'];
442 $ilDB->manipulateF(
443 "INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, shuffle) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
444 array(
445 "integer",
446 "integer",
447 "integer",
448 "text",
449 "float",
450 "integer",
451 "text",
452 "text"
453 ),
454 array(
455 $next_id,
456 $this->getId(),
457 $key,
458 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
459 $item->getPoints(),
460 $item->getOrder(),
461 $gap->getType(),
462 ($gap->getShuffle()) ? "1" : "0"
463 )
464 );
465 }
466
475 protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
476 {
477 global $DIC;
478 $ilDB = $DIC['ilDB'];
479
480 include_once "./Services/Math/classes/class.EvalMath.php";
481 $eval = new EvalMath();
482 $eval->suppress_errors = true;
483 $ilDB->manipulateF(
484 "INSERT INTO qpl_a_cloze (answer_id, question_fi, gap_id, answertext, points, aorder, cloze_type, lowerlimit, upperlimit, gap_size) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
485 array(
486 "integer",
487 "integer",
488 "integer",
489 "text",
490 "float",
491 "integer",
492 "text",
493 "text",
494 "text",
495 "integer"
496 ),
497 array(
498 $next_id,
499 $this->getId(),
500 $key,
501 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
502 $item->getPoints(),
503 $item->getOrder(),
504 $gap->getType(),
505 ($eval->e($item->getLowerBound() !== false) && strlen(
506 $item->getLowerBound()
507 ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
508 ($eval->e($item->getUpperBound() !== false) && strlen(
509 $item->getUpperBound()
510 ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
511 (int) $gap->getGapSize()
512 )
513 );
514 }
515
516
517
518 #endregion Save question to db
519
526 public function getGaps()
527 {
528 return $this->gaps;
529 }
530
531
538 public function flushGaps()
539 {
540 $this->gaps = array();
541 }
542
552 public function setClozeText($cloze_text = "")
553 {
554 $this->gaps = [];
555 $this->cloze_text = $this->cleanQuestiontext($cloze_text);
557 }
558
559 public function setClozeTextValue($cloze_text = "")
560 {
561 $this->cloze_text = $cloze_text;
562 }
563
571 public function getClozeText()
572 {
573 return $this->cloze_text;
574 }
575
584 public function getClozeTextForHTMLOutput() : string
585 {
586 $gaps = [];
587 preg_match_all('/\[gap\].*?\[\/gap\]/', $this->getClozeText(), $gaps);
588 $string_with_replaced_gaps = str_replace($gaps[0], '######GAP######', $this->getClozeText());
589 $cleaned_text = $this->getHtmlQuestionContentPurifier()->purify(
590 $string_with_replaced_gaps
591 );
592 $cleaned_text_with_gaps = preg_replace_callback('/######GAP######/', function ($match) use (&$gaps) {
593 return array_shift($gaps[0]);
594 }, $cleaned_text);
595
597 || !(new ilSetting('advanced_editing'))->get('advanced_editing_javascript_editor') === 'tinymce') {
598 $cleaned_text_with_gaps = nl2br($cleaned_text_with_gaps);
599 }
600
601 return $this->prepareTextareaOutput($cleaned_text_with_gaps, true);
602 }
603
611 public function getStartTag()
612 {
613 return $this->start_tag;
614 }
615
623 public function setStartTag($start_tag = "[gap]")
624 {
625 $this->start_tag = $start_tag;
626 }
627
635 public function getEndTag()
636 {
637 return $this->end_tag;
638 }
639
647 public function setEndTag($end_tag = "[/gap]")
648 {
649 $this->end_tag = $end_tag;
650 }
651
655 public function getFeedbackMode()
656 {
657 return $this->feedbackMode;
658 }
659
664 {
665 $this->feedbackMode = $feedbackMode;
666 }
667
675 {
676 include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
677 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
678 $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
679 preg_match_all($search_pattern, $this->getClozeText(), $found);
680 $this->gaps = array();
681 if (count($found[0])) {
682 foreach ($found[1] as $gap_index => $answers) {
683 // create text gaps by default
684 $gap = new assClozeGap(CLOZE_TEXT);
685 $textparams = preg_split("/(?<!\\\\),/", $answers);
686 foreach ($textparams as $key => $value) {
687 $answer = new assAnswerCloze($value, 0, $key);
688 $gap->addItem($answer);
689 }
690 $this->gaps[$gap_index] = $gap;
691 }
692 }
693 }
694
700 public function setGapType($gap_index, $gap_type)
701 {
702 if (array_key_exists($gap_index, $this->gaps)) {
703 $this->gaps[$gap_index]->setType($gap_type);
704 }
705 }
706
716 public function setGapShuffle($gap_index = 0, $shuffle = 1)
717 {
718 if (array_key_exists($gap_index, $this->gaps)) {
719 $this->gaps[$gap_index]->setShuffle($shuffle);
720 }
721 }
722
729 public function clearGapAnswers()
730 {
731 foreach ($this->gaps as $gap_index => $gap) {
732 $this->gaps[$gap_index]->clearItems();
733 }
734 }
735
743 public function getGapCount()
744 {
745 if (is_array($this->gaps)) {
746 return count($this->gaps);
747 } else {
748 return 0;
749 }
750 }
751
762 public function addGapAnswer($gap_index, $order, $answer)
763 {
764 if (array_key_exists($gap_index, $this->gaps)) {
765 if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC) {
766 // only allow notation with "." for real numbers
767 $answer = str_replace(",", ".", $answer);
768 }
769 $this->gaps[$gap_index]->addItem(new assAnswerCloze(trim($answer), 0, $order));
770 }
771 }
772
781 public function getGap($gap_index = 0)
782 {
783 if (array_key_exists($gap_index, $this->gaps)) {
784 return $this->gaps[$gap_index];
785 } else {
786 return null;
787 }
788 }
789
790 public function setGapSize($gap_index, $order, $size)
791 {
792 if (array_key_exists($gap_index, $this->gaps)) {
793 $this->gaps[$gap_index]->setGapSize($size);
794 }
795 }
796
807 public function setGapAnswerPoints($gap_index, $order, $points)
808 {
809 if (array_key_exists($gap_index, $this->gaps)) {
810 $this->gaps[$gap_index]->setItemPoints($order, $points);
811 }
812 }
813
822 public function addGapText($gap_index)
823 {
824 if (array_key_exists($gap_index, $this->gaps)) {
825 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
826 $answer = new assAnswerCloze(
827 "",
828 0,
829 $this->gaps[$gap_index]->getItemCount()
830 );
831 $this->gaps[$gap_index]->addItem($answer);
832 }
833 }
834
843 public function addGapAtIndex($gap, $index)
844 {
845 $this->gaps[$index] = $gap;
846 }
847
858 public function setGapAnswerLowerBound($gap_index, $order, $bound)
859 {
860 if (array_key_exists($gap_index, $this->gaps)) {
861 $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
862 }
863 }
864
875 public function setGapAnswerUpperBound($gap_index, $order, $bound)
876 {
877 if (array_key_exists($gap_index, $this->gaps)) {
878 $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
879 }
880 }
881
888 public function getMaximumPoints()
889 {
890 $assClozeGapCombinationObj = new assClozeGapCombination();
891 $points = 0;
892 $gaps_used_in_combination = array();
893 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
894 $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
895 $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
896 }
897 foreach ($this->gaps as $gap_index => $gap) {
898 if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
899 if ($gap->getType() == CLOZE_TEXT) {
900 $gap_max_points = 0;
901 foreach ($gap->getItems($this->getShuffler()) as $item) {
902 if ($item->getPoints() > $gap_max_points) {
903 $gap_max_points = $item->getPoints();
904 }
905 }
906 $points += $gap_max_points;
907 } elseif ($gap->getType() == CLOZE_SELECT) {
908 $srpoints = 0;
909 foreach ($gap->getItems($this->getShuffler()) as $item) {
910 if ($item->getPoints() > $srpoints) {
911 $srpoints = $item->getPoints();
912 }
913 }
914 $points += $srpoints;
915 } elseif ($gap->getType() == CLOZE_NUMERIC) {
916 $numpoints = 0;
917 foreach ($gap->getItems($this->getShuffler()) as $item) {
918 if ($item->getPoints() > $numpoints) {
919 $numpoints = $item->getPoints();
920 }
921 }
922 $points += $numpoints;
923 }
924 }
925 }
926
927 return $points;
928 }
929
935 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
936 {
937 if ($this->id <= 0) {
938 // The question has not been saved. It cannot be duplicated
939 return;
940 }
941 // duplicate the question in database
942 $this_id = $this->getId();
943 $thisObjId = $this->getObjId();
944
945 $clone = $this;
946 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
948 $clone->id = -1;
949
950 if ((int) $testObjId > 0) {
951 $clone->setObjId($testObjId);
952 }
953
954 if ($title) {
955 $clone->setTitle($title);
956 }
957 if ($author) {
958 $clone->setAuthor($author);
959 }
960 if ($owner) {
961 $clone->setOwner($owner);
962 }
963 if ($for_test) {
964 $clone->saveToDb($original_id);
965 } else {
966 $clone->saveToDb();
967 }
968 if ($this->gap_combinations_exists) {
969 $this->copyGapCombination($this_id, $clone->getId());
970 }
971 if ($for_test) {
972 $clone->saveToDb($original_id);
973 } else {
974 $clone->saveToDb();
975 }
976 // copy question page content
977 $clone->copyPageOfQuestion($this_id);
978 // copy XHTML media objects
979 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
980
981 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
982
983 return $clone->getId();
984 }
985
991 public function copyObject($target_questionpool_id, $title = "")
992 {
993 if ($this->getId() <= 0) {
994 // The question has not been saved. It cannot be duplicated
995 return;
996 }
997
998 $thisId = $this->getId();
999 $thisObjId = $this->getObjId();
1000
1001 $clone = $this;
1002 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
1004 $clone->id = -1;
1005 $clone->setObjId($target_questionpool_id);
1006 if ($title) {
1007 $clone->setTitle($title);
1008 }
1009
1010 $clone->saveToDb();
1011
1012 if ($this->gap_combinations_exists) {
1013 $this->copyGapCombination($original_id, $clone->getId());
1014 $clone->saveToDb();
1015 }
1016
1017 // copy question page content
1018 $clone->copyPageOfQuestion($original_id);
1019 // copy XHTML media objects
1020 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
1021
1022 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
1023
1024 return $clone->getId();
1025 }
1026
1027 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
1028 {
1029 if ($this->id <= 0) {
1030 // The question has not been saved. It cannot be duplicated
1031 return;
1032 }
1033
1034 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
1035
1036 $sourceQuestionId = $this->id;
1037 $sourceParentId = $this->getObjId();
1038
1039 // duplicate the question in database
1040 $clone = $this;
1041 $clone->id = -1;
1042
1043 $clone->setObjId($targetParentId);
1044
1045 if ($targetQuestionTitle) {
1046 $clone->setTitle($targetQuestionTitle);
1047 }
1048
1049 $clone->saveToDb();
1050
1051 if ($this->gap_combinations_exists) {
1052 $this->copyGapCombination($sourceQuestionId, $clone->getId());
1053 $clone->saveToDb();
1054 }
1055 // copy question page content
1056 $clone->copyPageOfQuestion($sourceQuestionId);
1057 // copy XHTML media objects
1058 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
1059
1060 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
1061
1062 return $clone->id;
1063 }
1064
1065 public function copyGapCombination($orgID, $newID)
1066 {
1067 $assClozeGapCombinationObj = new assClozeGapCombination();
1068 $array = $assClozeGapCombinationObj->loadFromDb($orgID);
1069 $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array);
1070 }
1071
1077 public function updateClozeTextFromGaps()
1078 {
1079 $output = $this->getClozeText();
1080 foreach ($this->getGaps() as $gap_index => $gap) {
1081 $answers = array();
1082 foreach ($gap->getItemsRaw() as $item) {
1083 array_push($answers, str_replace([',', '['], ["\\,", '[&hairsp;'], $item->getAnswerText()));
1084 }
1085 // fau: fixGapReplace - use replace function
1086 $output = $this->replaceFirstGap($output, "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]");
1087 // fau.
1088 }
1089 $output = str_replace("_gap]", "gap]", $output);
1090 $this->cloze_text = $output;
1091 }
1092
1102 public function deleteAnswerText($gap_index, $answer_index)
1103 {
1104 if (array_key_exists($gap_index, $this->gaps)) {
1105 if ($this->gaps[$gap_index]->getItemCount() == 1) {
1106 // this is the last answer text => remove the gap
1107 $this->deleteGap($gap_index);
1108 } else {
1109 // remove the answer text
1110 $this->gaps[$gap_index]->deleteItem($answer_index);
1111 $this->updateClozeTextFromGaps();
1112 }
1113 }
1114 }
1115
1124 public function deleteGap($gap_index)
1125 {
1126 if (array_key_exists($gap_index, $this->gaps)) {
1127 $output = $this->getClozeText();
1128 foreach ($this->getGaps() as $replace_gap_index => $gap) {
1129 $answers = array();
1130 foreach ($gap->getItemsRaw() as $item) {
1131 array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1132 }
1133 if ($replace_gap_index == $gap_index) {
1134 // fau: fixGapReplace - use replace function
1135 $output = $this->replaceFirstGap($output, '');
1136 // fau.
1137 } else {
1138 // fau: fixGapReplace - use replace function
1139 $output = $this->replaceFirstGap($output, "[_gap]" . join(",", $answers) . "[/_gap]");
1140 // fau.
1141 }
1142 }
1143 $output = str_replace("_gap]", "gap]", $output);
1144 $this->cloze_text = $output;
1145 unset($this->gaps[$gap_index]);
1146 $this->gaps = array_values($this->gaps);
1147 }
1148 }
1149
1159 public function getTextgapPoints($a_original, $a_entered, $max_points)
1160 {
1161 include_once "./Services/Utilities/classes/class.ilStr.php";
1162 global $DIC;
1163 $refinery = $DIC->refinery();
1164 $result = 0;
1165 $gaprating = $this->getTextgapRating();
1166
1167 switch ($gaprating) {
1169 if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) {
1170 $result = $max_points;
1171 }
1172 break;
1174 if (strcmp($a_original, $a_entered) == 0) {
1175 $result = $max_points;
1176 }
1177 break;
1179 $transformation = $refinery->string()->levenshtein()->standard($a_original, 1);
1180 break;
1182 $transformation = $refinery->string()->levenshtein()->standard($a_original, 2);
1183 break;
1185 $transformation = $refinery->string()->levenshtein()->standard($a_original, 3);
1186 break;
1188 $transformation = $refinery->string()->levenshtein()->standard($a_original, 4);
1189 break;
1191 $transformation = $refinery->string()->levenshtein()->standard($a_original, 5);
1192 break;
1193 }
1194
1195 // run answers against Levenshtein2 methods
1196 if (isset($transformation) && $transformation->transform($a_entered) >= 0) {
1197 $result = $max_points;
1198 }
1199 return $result;
1200 }
1201
1202
1212 public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1213 {
1214 // fau: fixGapFormula - check entered value by evalMath
1215 // if( ! $this->checkForValidFormula($a_entered) )
1216 // {
1217 // return 0;
1218 // }
1219
1220 include_once "./Services/Math/classes/class.EvalMath.php";
1221 $eval = new EvalMath();
1222 $eval->suppress_errors = true;
1223 $result = 0;
1224
1225 if ($eval->e($a_entered) === false) {
1226 return 0;
1227 } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) {
1228 // fau.
1229 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1230 $result = $max_points;
1231 }
1232 } elseif ($eval->e($lowerBound) !== false) {
1233 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
1234 $result = $max_points;
1235 }
1236 } elseif ($eval->e($upperBound) !== false) {
1237 if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1238 $result = $max_points;
1239 }
1240 } else {
1241 if ($eval->e($a_entered) == $eval->e($a_original)) {
1242 $result = $max_points;
1243 }
1244 }
1245 return $result;
1246 }
1247
1252 public function checkForValidFormula($value)
1253 {
1254 return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1255 }
1266 public function calculateReachedPoints($active_id, $pass = null, $authorized = true, $returndetails = false)
1267 {
1268 global $DIC;
1269 $ilDB = $DIC['ilDB'];
1270
1271 if (is_null($pass)) {
1272 $pass = $this->getSolutionMaxPass($active_id);
1273 }
1274
1275 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1276 $user_result = array();
1277 while ($data = $ilDB->fetchAssoc($result)) {
1278 if (strcmp($data["value2"], "") != 0) {
1279 $user_result[$data["value1"]] = array(
1280 "gap_id" => $data["value1"],
1281 "value" => $data["value2"]
1282 );
1283 }
1284 }
1285
1286 ksort($user_result); // this is required when identical scoring for same solutions is disabled
1287
1288 if ($returndetails) {
1289 $detailed = array();
1290 $this->calculateReachedPointsForSolution($user_result, $detailed);
1291 return $detailed;
1292 }
1293
1294 return $this->calculateReachedPointsForSolution($user_result);
1295 }
1296
1297 protected function isValidNumericSubmitValue($submittedValue)
1298 {
1299 if (is_numeric($submittedValue)) {
1300 return true;
1301 }
1302
1303 if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1304 return true;
1305 }
1306
1307 return false;
1308 }
1309
1310 public function validateSolutionSubmit()
1311 {
1312 foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) {
1313 $gap = $this->getGap($gapIndex);
1314
1315 if ($gap->getType() != CLOZE_NUMERIC) {
1316 continue;
1317 }
1318
1319 if (strlen($value) && !$this->isValidNumericSubmitValue($value)) {
1320 ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1321 return false;
1322 }
1323 }
1324
1325 return true;
1326 }
1327
1328 public function fetchSolutionSubmit($submit)
1329 {
1330 $solutionSubmit = array();
1331 foreach ($submit as $key => $value) {
1332 if ($value === null || is_array($value)) {
1333 continue;
1334 }
1335
1336 $trimmed_value = trim($value);
1337 if ($trimmed_value === '') {
1338 continue;
1339 }
1340
1341 if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1342 $gap = $this->getGap($matches[1]);
1343 if (!is_object($gap)
1344 || $gap->getType() == CLOZE_SELECT && $trimmed_value == -1) {
1345 continue;
1346 }
1347
1348 if ($gap->getType() == CLOZE_NUMERIC && !is_numeric(str_replace(",", ".", $trimmed_value))) {
1349 $trimmed_value = null;
1350 } elseif ($gap->getType() == CLOZE_NUMERIC) {
1351 $trimmed_value = str_replace(",", ".", $trimmed_value);
1352 }
1353 $solutionSubmit[trim($matches[1])] = $trimmed_value;
1354 }
1355 }
1356
1357 return $solutionSubmit;
1358 }
1359
1361 {
1362 $submit = $_POST;
1363 $solutionSubmit = array();
1364
1365 foreach ($submit as $key => $value) {
1366 if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1367 if ($value !== null && $value !== '') {
1368 $gap = $this->getGap($matches[1]);
1369 if (is_object($gap)) {
1370 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1371 if ($gap->getType() == CLOZE_NUMERIC) {
1372 $value = str_replace(",", ".", $value);
1373 }
1374 $solutionSubmit[trim($matches[1])] = $value;
1375 }
1376 }
1377 }
1378 }
1379 }
1380
1381 return $solutionSubmit;
1382 }
1383
1384 public function getSolutionSubmit()
1385 {
1386 return $this->fetchSolutionSubmit($_POST);
1387 }
1388
1397 public function saveWorkingData($active_id, $pass = null, $authorized = true)
1398 {
1399 global $DIC;
1400 $ilDB = $DIC['ilDB'];
1401 $ilUser = $DIC['ilUser'];
1402 if (is_null($pass)) {
1403 include_once "./Modules/Test/classes/class.ilObjTest.php";
1404 $pass = ilObjTest::_getPass($active_id);
1405 }
1406
1407 $entered_values = 0;
1408
1409 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
1410 $this->removeCurrentSolution($active_id, $pass, $authorized);
1411
1412 foreach ($this->getSolutionSubmit() as $key => $value) {
1413 if ($value !== null && $value !== '') {
1414 $gap = $this->getGap($key);
1415 if (is_object($gap)) {
1416 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1417 $this->saveCurrentSolution($active_id, $pass, $key, $value, $authorized);
1418 $entered_values++;
1419 }
1420 }
1421 }
1422 }
1423 });
1424
1425 if ($entered_values) {
1426 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1428 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1429 }
1430 } else {
1431 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1433 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1434 }
1435 }
1436
1437 return true;
1438 }
1439
1446 public function getQuestionType()
1447 {
1448 return "assClozeTest";
1449 }
1450
1458 public function getTextgapRating()
1459 {
1460 return $this->textgap_rating;
1461 }
1462
1470 public function setTextgapRating($a_textgap_rating)
1471 {
1472 switch ($a_textgap_rating) {
1480 $this->textgap_rating = $a_textgap_rating;
1481 break;
1482 default:
1483 $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1484 break;
1485 }
1486 }
1487
1495 public function getIdenticalScoring()
1496 {
1497 return ($this->identical_scoring) ? 1 : 0;
1498 }
1499
1507 public function setIdenticalScoring($a_identical_scoring)
1508 {
1509 $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1510 }
1511
1518 public function getAdditionalTableName()
1519 {
1520 return "qpl_qst_cloze";
1521 }
1522
1529 public function getAnswerTableName()
1530 {
1531 return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1532 }
1533
1540 public function setFixedTextLength($a_text_len)
1541 {
1542 $this->fixedTextLength = $a_text_len;
1543 }
1544
1551 public function getFixedTextLength()
1552 {
1554 }
1555
1564 public function getMaximumGapPoints($gap_index)
1565 {
1566 $points = 0;
1567 $gap_max_points = 0;
1568 if (array_key_exists($gap_index, $this->gaps)) {
1569 $gap = &$this->gaps[$gap_index];
1570 foreach ($gap->getItems($this->getShuffler()) as $answer) {
1571 if ($answer->getPoints() > $gap_max_points) {
1572 $gap_max_points = $answer->getPoints();
1573 }
1574 }
1575 $points += $gap_max_points;
1576 }
1577 return $points;
1578 }
1579
1585 {
1586 return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1587 }
1589 {
1591 }
1592
1593 public function getGapCombinations()
1594 {
1596 }
1597
1598 public function setGapCombinationsExists($value)
1599 {
1600 $this->gap_combinations_exists = $value;
1601 }
1602
1603 public function setGapCombinations($value)
1604 {
1605 $this->gap_combinations = $value;
1606 }
1607
1611 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1612 {
1613 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1614
1615 $solution = $this->getSolutionValues($active_id, $pass);
1616 $i = 1;
1617 foreach ($this->getGaps() as $gap_index => $gap) {
1618 $worksheet->setCell($startrow + $i, 0, $this->lng->txt("gap") . " $i");
1619 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1620 $checked = false;
1621 foreach ($solution as $solutionvalue) {
1622 if ($gap_index == $solutionvalue["value1"]) {
1623 $string_escaping_org_value = $worksheet->getStringEscaping();
1624 try {
1625 $worksheet->setStringEscaping(false);
1626
1627 switch ($gap->getType()) {
1628 case CLOZE_SELECT:
1629 $worksheet->setCell($startrow + $i, 2, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1630 break;
1631 case CLOZE_NUMERIC:
1632 case CLOZE_TEXT:
1633 $worksheet->setCell($startrow + $i, 2, $solutionvalue["value2"]);
1634 break;
1635 }
1636 } finally {
1637 $worksheet->setStringEscaping($string_escaping_org_value);
1638 }
1639 }
1640 }
1641 $i++;
1642 }
1643
1644 return $startrow + $i + 1;
1645 }
1646
1651 {
1652 // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1653 //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1654 $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1655 // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1656 }
1657
1661 public function toJSON()
1662 {
1663 include_once("./Services/RTE/classes/class.ilRTE.php");
1664 $result = array();
1665 $result['id'] = (int) $this->getId();
1666 $result['type'] = (string) $this->getQuestionType();
1667 $result['title'] = (string) $this->getTitle();
1668 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1669 $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1670 $result['nr_of_tries'] = (int) $this->getNrOfTries();
1671 $result['shuffle'] = (bool) $this->getShuffle();
1672 $result['feedback'] = array(
1673 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1674 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1675 );
1676
1677 $gaps = array();
1678 foreach ($this->getGaps() as $key => $gap) {
1679 $items = array();
1680 foreach ($gap->getItems($this->getShuffler()) as $item) {
1681 $jitem = array();
1682 $jitem['points'] = $item->getPoints();
1683 $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1684 $jitem['order'] = $item->getOrder();
1685 if ($gap->getType() == CLOZE_NUMERIC) {
1686 $jitem['lowerbound'] = $item->getLowerBound();
1687 $jitem['upperbound'] = $item->getUpperBound();
1688 } else {
1689 $jitem['value'] = trim($jitem['value']);
1690 }
1691 array_push($items, $jitem);
1692 }
1693
1694 if ($gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC)) {
1695 $jgap['size'] = $gap->getGapSize();
1696 }
1697
1698 $jgap['shuffle'] = $gap->getShuffle();
1699 $jgap['type'] = $gap->getType();
1700 $jgap['item'] = $items;
1701
1702 array_push($gaps, $jgap);
1703 }
1704 $result['gaps'] = $gaps;
1705 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1706 $result['mobs'] = $mobs;
1707 return json_encode($result);
1708 }
1709
1718 public function getOperators($expression)
1719 {
1720 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1722 }
1723
1728 public function getExpressionTypes()
1729 {
1730 return array(
1736 );
1737 }
1738
1747 public function getUserQuestionResult($active_id, $pass)
1748 {
1750 global $DIC;
1751 $ilDB = $DIC['ilDB'];
1752 $result = new ilUserQuestionResult($this, $active_id, $pass);
1753
1754 $maxStep = $this->lookupMaxStep($active_id, $pass);
1755
1756 if ($maxStep !== null) {
1757 $data = $ilDB->queryF(
1758 "
1759 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1760 FROM tst_solutions sol
1761 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1762 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1763 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1764 ",
1765 array("integer", "integer", "integer","integer"),
1766 array($active_id, $pass, $this->getId(), $maxStep)
1767 );
1768 } else {
1769 $data = $ilDB->queryF(
1770 "
1771 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1772 FROM tst_solutions sol
1773 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1774 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1775 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1776 ",
1777 array("integer", "integer", "integer"),
1778 array($active_id, $pass, $this->getId())
1779 );
1780 }
1781
1782 while ($row = $ilDB->fetchAssoc($data)) {
1783 if ($row["cloze_type"] == 1) {
1784 $row["value2"]++;
1785 }
1786 $result->addKeyValue($row["val"], $row["value2"]);
1787 }
1788
1789 $points = $this->calculateReachedPoints($active_id, $pass);
1790 $max_points = $this->getMaximumPoints();
1791
1792 $result->setReachedPercentage(($points / $max_points) * 100);
1793
1794 return $result;
1795 }
1796
1805 public function getAvailableAnswerOptions($index = null)
1806 {
1807 if ($index !== null) {
1808 return $this->getGap($index);
1809 } else {
1810 return $this->getGaps();
1811 }
1812 }
1813
1814 public function calculateCombinationResult($user_result)
1815 {
1816 $points = 0;
1817
1818 $assClozeGapCombinationObj = new assClozeGapCombination();
1819
1820 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1821 $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1822 $gap_answers = array();
1823 $gap_used_in_combination = array();
1824 foreach ($user_result as $user_result_build_list) {
1825 if (is_array($user_result_build_list)) {
1826 $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1827 }
1828 }
1829
1830 foreach ($combinations_for_question as $combination) {
1831 foreach ($combination as $row_key => $row_answers) {
1832 $combination_fulfilled = true;
1833 $points_for_combination = $row_answers['points'];
1834 foreach ($row_answers as $gap_key => $combination_gap_answer) {
1835 if ($gap_key !== 'points') {
1836 $gap_used_in_combination[$gap_key] = $gap_key;
1837 }
1838 if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1839 switch ($combination_gap_answer['type']) {
1840 case CLOZE_TEXT:
1841 $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1842 if ($is_text_gap_correct != 1) {
1843 $combination_fulfilled = false;
1844 }
1845 break;
1846 case CLOZE_SELECT:
1847 $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1848 $answertext = $answer->getAnswertext();
1849 if ($answertext != $combination_gap_answer['answer']) {
1850 $combination_fulfilled = false;
1851 }
1852 break;
1853 case CLOZE_NUMERIC:
1854 $answer = $this->gaps[$gap_key]->getItem(0);
1855 if ($combination_gap_answer['answer'] != 'out_of_bound') {
1856 $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1857 if ($is_numeric_gap_correct != 1) {
1858 $combination_fulfilled = false;
1859 }
1860 } else {
1861 $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1862 if ($wrong_is_the_new_right == 1) {
1863 $combination_fulfilled = false;
1864 }
1865 }
1866 break;
1867 }
1868 } else {
1869 if ($gap_key !== 'points') {
1870 $combination_fulfilled = false;
1871 }
1872 }
1873 }
1874 if ($combination_fulfilled) {
1875 $points += $points_for_combination;
1876 }
1877 }
1878 }
1879 }
1880 return array($points, $gap_used_in_combination);
1881 }
1887 protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1888 {
1889 if ($detailed === null) {
1890 $detailed = array();
1891 }
1892
1893 $assClozeGapCombinationObj = new assClozeGapCombination();
1894 $combinations[1] = array();
1895 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1896 $combinations = $this->calculateCombinationResult($user_result);
1897 $points = $combinations[0];
1898 }
1899 $counter = 0;
1900 $solution_values_text = array(); // for identical scoring checks
1901 $solution_values_select = array(); // for identical scoring checks
1902 $solution_values_numeric = array(); // for identical scoring checks
1903 foreach ($user_result as $gap_id => $value) {
1904 if (is_string($value)) {
1905 $value = array("value" => $value);
1906 }
1907
1908 if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1909 switch ($this->gaps[$gap_id]->getType()) {
1910 case CLOZE_TEXT:
1911 $gappoints = 0;
1912 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1913 $answer = $this->gaps[$gap_id]->getItem($order);
1914 $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1915 if ($gotpoints > $gappoints) {
1916 $gappoints = $gotpoints;
1917 }
1918 }
1919 if (!$this->getIdenticalScoring()) {
1920 // check if the same solution text was already entered
1921 if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0)) {
1922 $gappoints = 0;
1923 }
1924 }
1925 $points += $gappoints;
1926 $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1927 array_push($solution_values_text, $value["value"]);
1928 break;
1929 case CLOZE_NUMERIC:
1930 $gappoints = 0;
1931 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1932 $answer = $this->gaps[$gap_id]->getItem($order);
1933 $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1934 if ($gotpoints > $gappoints) {
1935 $gappoints = $gotpoints;
1936 }
1937 }
1938 if (!$this->getIdenticalScoring()) {
1939 // check if the same solution value was already entered
1940 include_once "./Services/Math/classes/class.EvalMath.php";
1941 $eval = new EvalMath();
1942 $eval->suppress_errors = true;
1943 $found_value = false;
1944 foreach ($solution_values_numeric as $solval) {
1945 if ($eval->e($solval) == $eval->e($value["value"])) {
1946 $found_value = true;
1947 }
1948 }
1949 if ($found_value && ($gappoints > 0)) {
1950 $gappoints = 0;
1951 }
1952 }
1953 $points += $gappoints;
1954 $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1955 array_push($solution_values_numeric, $value["value"]);
1956 break;
1957 case CLOZE_SELECT:
1958 if ($value["value"] >= 0) {
1959 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1960 $answer = $this->gaps[$gap_id]->getItem($order);
1961 if ($value["value"] == $answer->getOrder()) {
1962 $answerpoints = $answer->getPoints();
1963 if (!$this->getIdenticalScoring()) {
1964 // check if the same solution value was already entered
1965 if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0)) {
1966 $answerpoints = 0;
1967 }
1968 }
1969 $points += $answerpoints;
1970 $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? true : false, "positive" => ($answerpoints > 0) ? true : false);
1971 array_push($solution_values_select, $answer->getAnswertext());
1972 }
1973 }
1974 }
1975 break;
1976 }
1977 }
1978 }
1979
1980 return $points;
1981 }
1982
1984 {
1985 $userSolution = array();
1986
1987 foreach ($previewSession->getParticipantsSolution() as $key => $val) {
1988 $userSolution[$key] = array('gap_id' => $key, 'value' => $val);
1989 }
1990
1991 $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
1992 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
1993
1994 return $this->ensureNonNegativePoints($reachedPoints);
1995 }
1996
1997 public function fetchAnswerValueForGap($userSolution, $gapIndex)
1998 {
1999 $answerValue = '';
2000
2001 foreach ($userSolution as $value1 => $value2) {
2002 if ($value1 == $gapIndex) {
2003 $answerValue = $value2;
2004 break;
2005 }
2006 }
2007
2008 return $answerValue;
2009 }
2010
2011 public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
2012 {
2013 $gap = $this->getGap($qIndex);
2014
2015 if ($gap->getType() != CLOZE_TEXT) {
2016 return false;
2017 }
2018
2019 foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) {
2020 if ($item->getAnswertext() === $answerOptionValue) {
2021 return false;
2022 }
2023 }
2024
2025 return true;
2026 }
2027
2028 public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
2029 {
2030 $gap = $this->getGap($qIndex); /* @var assClozeGap $gap */
2031
2032 $item = new assAnswerCloze($answerOptionValue, $points);
2033 $item->setOrder($gap->getItemCount());
2034
2035 $gap->addItem($item);
2036 }
2037
2038 public function savePartial()
2039 {
2040 return true;
2041 }
2042}
$result
$size
Definition: RandomTest.php:84
$_POST["username"]
An exception for terminatinating execution or to throw for unit testing.
Class for cloze question numeric answers.
Class for cloze question gaps.
Class for cloze tests.
clearGapAnswers()
Removes all answers from the gaps.
getEndTag()
Returns the end tag of a cloze gap.
addGapAnswer($gap_index, $order, $answer)
Sets the answer text of a gap with a given index.
isComplete()
Returns TRUE, if a cloze test is complete for use.
setGapShuffle($gap_index=0, $shuffle=1)
Sets the shuffle state of a gap with a given index.
getClozeText()
Returns the cloze text.
getTextgapPoints($a_original, $a_entered, $max_points)
Returns the points for a text gap and compares the given solution with the entered solution using the...
isValidNumericSubmitValue($submittedValue)
setGapAnswerPoints($gap_index, $order, $points)
Sets the points of a gap with a given index and an answer with a given order.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
addAnswerOptionValue($qIndex, $answerOptionValue, $points)
setFeedbackMode($feedbackMode)
copyObject($target_questionpool_id, $title="")
Copies an assClozeTest object.
setClozeTextValue($cloze_text="")
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
getRTETextWithMediaObjects()
Collects all text in the question which could contain media objects which were created with the Rich ...
addGapAtIndex($gap, $index)
Adds a ClozeGap object at a given index.
setTextgapRating($a_textgap_rating)
Sets the rating option for text gaps.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
setGapSize($gap_index, $order, $size)
setGapAnswerUpperBound($gap_index, $order, $bound)
Sets the upper bound of a gap with a given index and an answer with a given order.
createGapsFromQuestiontext()
Create gap entries by parsing the question text.
getAnswerTableName()
Returns the name of the answer table in the database.
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
updateClozeTextFromGaps()
Updates the gap parameters in the cloze text from the form input.
flushGaps()
Deletes all gaps without changing the cloze text.
getStartTag()
Returns the start tag of a cloze gap.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
saveAnswerSpecificDataToDb()
Save all gaps to the database.
getFixedTextLength()
Gets the fixed text length for all text fields in the cloze question.
addGapText($gap_index)
Adds a new answer text value to a text gap with a given index.
setIdenticalScoring($a_identical_scoring)
Sets the identical scoring option for cloze questions.
saveToDb($original_id="")
Saves a assClozeTest object to a database.
getGaps()
Returns the array of gaps.
getOperators($expression)
Get all available operations for a specific question.
__construct( $title="", $comment="", $author="", $owner=-1, $question="")
assClozeTest constructor
getQuestionType()
Returns the question type of the question.
checkForValidFormula($value)
getTextgapRating()
Returns the rating option for text gaps.
deleteGap($gap_index)
Deletes a gap with a given index.
getGap($gap_index=0)
Returns the gap at a given index.
setGapType($gap_index, $gap_type)
Set the type of a gap with a given index.
setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
{Creates an Excel worksheet for the detailed cumulated results of this question.object}
calculateReachedPoints($active_id, $pass=null, $authorized=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
copyGapCombination($orgID, $newID)
calculateReachedPointsForSolution($user_result, &$detailed=null)
getExpressionTypes()
Get all available expression types for a specific question.
isAddableAnswerOptionValue($qIndex, $answerOptionValue)
saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
getGapCount()
Returns the number of gaps.
setGapCombinationsExists($value)
saveClozeGapItemsToDb($gap, $key)
Save all items belonging to one cloze gap to the db.
setGapAnswerLowerBound($gap_index, $order, $bound)
Sets the lower bound of a gap with a given index and an answer with a given order.
setEndTag($end_tag="[/gap]")
Sets the end tag of a cloze gap.
fetchAnswerValueForGap($userSolution, $gapIndex)
getMaximumGapPoints($gap_index)
Returns the maximum points for a gap.
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
deleteAnswerText($gap_index, $answer_index)
Deletes the answer text of a gap with a given index and an answer with a given order.
saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
fetchSolutionSubmit($submit)
toJSON()
Returns a JSON representation of the question.
loadFromDb($question_id)
Loads a assClozeTest object from a database.
setFixedTextLength($a_text_len)
Sets a fixed text length for all text fields in the cloze question.
calculateCombinationResult($user_result)
setClozeText($cloze_text="")
Evaluates the text gap solutions from the cloze text.
saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
Saves a gap-item record.
replaceFirstGap($gaptext, $content)
Replace the first gap in a string without treating backreferences.
setStartTag($start_tag="[gap]")
Sets the start tag of a cloze gap.
getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
Returns the points for a text gap and compares the given solution with the entered solution using the...
duplicate($for_test=true, $title="", $author="", $owner="", $testObjId=null)
Duplicates an assClozeTest.
cleanQuestiontext($text)
Cleans cloze question text to remove attributes or tags from older ILIAS versions.
getIdenticalScoring()
Returns the identical scoring status of the question.
getClozeTextForHTMLOutput()
Returns the cloze text as HTML (with optional nl2br) Fix for Mantis 29987: We assume Tiny embeds any ...
Abstract basic class which is to be extended by the concrete assessment question type classes.
getCurrentSolutionResultSet($active_id, $pass, $authorized=true)
Get a restulset for the current user solution for a this question by active_id and pass.
getSolutionValues($active_id, $pass=null, $authorized=true)
Loads solutions of a given user from the database an returns it.
static _getOriginalId($question_id)
Returns the original id of a question.
formatSAQuestion($a_q)
Format self assessment question.
setId($id=-1)
Sets the id of the assQuestion object.
setOriginalId($original_id)
setObjId($obj_id=0)
Set the object id of the container object.
getSolutionMaxPass($active_id)
Returns the maximum pass a users question solution.
saveQuestionDataToDb($original_id="")
getId()
Gets the id of the assQuestion object.
saveCurrentSolution($active_id, $pass, $value1, $value2, $authorized=true, $tstamp=null)
getObjId()
Get the object id of the container object.
setTitle($title="")
Sets the title string of the assQuestion object.
setOwner($owner="")
Sets the creator/owner ID of the assQuestion object.
setEstimatedWorkingTime($hour=0, $min=0, $sec=0)
Sets the estimated working time of a question from given hour, minute and second.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
static logAction($logtext="", $active_id="", $question_id="")
Logs an action into the Test&Assessment log.
removeCurrentSolution($active_id, $pass, $authorized=true)
setAuthor($author="")
Sets the authors name of the assQuestion object.
prepareTextareaOutput($txt_output, $prepare_for_latex_output=false, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
getShuffle()
Gets the shuffle flag.
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getTitle()
Gets the title string of the assQuestion object.
setPoints($a_points)
Sets the maximum available points for the question.
isAdditionalContentEditingModePageObject()
isser for additional "pageobject" content editing mode
setComment($comment="")
Sets the comment string of the assQuestion object.
getAuthor()
Gets the authors name of the assQuestion object.
setNrOfTries($a_nr_of_tries)
getQuestion()
Gets the question string of the question object.
setAdditionalContentEditingMode($additinalContentEditingMode)
setter for additional content editing mode for this question
setQuestion($question="")
Sets the question string of the question object.
ensureNonNegativePoints($points)
const FB_MODE_GAP_QUESTION
constants for different feedback modes (per gap or per gap-answers/options)
static _getLogLanguage()
retrieve the log language for assessment logging
static _enabledAssessmentLogging()
check wether assessment logging is enabled or not
static _getMobsOfObject($a_type, $a_id, $a_usage_hist_nr=0, $a_lang="-")
get mobs of object
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static _replaceMediaObjectImageSrc($a_text, $a_direction=0, $nic=IL_INST_ID)
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
ILIAS Setting Class.
static strToLower($a_string)
Definition: class.ilStr.php:87
Class ilUserQuestionResult.
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
global $DIC
Definition: goto.php:24
$mobs
Definition: imgupload.php:54
$ilUser
Definition: imgupload.php:18
const TEXTGAP_RATING_LEVENSHTEIN5
const TEXTGAP_RATING_LEVENSHTEIN4
const TEXTGAP_RATING_LEVENSHTEIN3
const TEXTGAP_RATING_CASESENSITIVE
const CLOZE_NUMERIC
const TEXTGAP_RATING_LEVENSHTEIN2
const TEXTGAP_RATING_CASEINSENSITIVE
const CLOZE_SELECT
const TEXTGAP_RATING_LEVENSHTEIN1
const CLOZE_TEXT
Cloze question constants.
Class iQuestionCondition.
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
Interface ilObjAnswerScoringAdjustable.
Interface ilObjQuestionScoringAdjustable.
$index
Definition: metadata.php:128
$i
Definition: metadata.php:24
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$query
global $ilDB
$data
Definition: storeScorm.php:23