ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
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);$text = preg_replace("/\[gap[^\]]*?\]/", "[gap]", $text);
163 $text = preg_replace("/<gap([^>]*?)>/", "[gap]", $text);
164 $text = str_replace("</gap>", "[/gap]", $text);$text = str_replace('GAPMASKEDDOLLAR', '$', $text);
165// fau.
166 return $text;
167 }
168
169 // fau: fixGapReplace - add function replaceFirstGap()
176 public function replaceFirstGap($gaptext, $content)
177 {
178 $content = str_replace('$','GAPMASKEDDOLLAR', $content);
179 $output = preg_replace("/\[gap\].*?\[\/gap\]/", $content, $gaptext, 1);
180 $output = str_replace('GAPMASKEDDOLLAR', '$', $output);
181
182 return $output;
183 }
184// fau.
191 public function loadFromDb($question_id)
192 {
193 global $DIC;
194 $ilDB = $DIC['ilDB'];
195 $result = $ilDB->queryF(
196 "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",
197 array("integer"),
198 array($question_id)
199 );
200 if ($result->numRows() == 1) {
201 $data = $ilDB->fetchAssoc($result);
202 $this->setId($question_id);
203 $this->setNrOfTries($data['nr_of_tries']);
204 $this->setObjId($data["obj_fi"]);
205 $this->setTitle($data["title"]);
206 $this->setComment($data["description"]);
207 $this->setOriginalId($data["original_id"]);
208 $this->setAuthor($data["author"]);
209 $this->setPoints($data["points"]);
210 $this->setOwner($data["owner"]);
211 $this->setQuestion($this->cleanQuestiontext($data["question_text"]));
212 $this->setClozeText($data['cloze_text']);
213 $this->setFixedTextLength($data["fixed_textlen"]);
214 $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]);
215 $this->setFeedbackMode($data['feedback_mode'] === null ? ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION : $data['feedback_mode']);
216
217 try {
218 $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
221 }
222
223 // replacement of old syntax with new syntax
224 include_once("./Services/RTE/classes/class.ilRTE.php");
225 $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
226 $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
227 $this->setTextgapRating($data["textgap_rating"]);
228 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
229
230 try {
231 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
233 }
234
235 // open the cloze gaps with all answers
236 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
237 include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
238 $result = $ilDB->queryF(
239 "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
240 array("integer"),
241 array($question_id)
242 );
243 if ($result->numRows() > 0) {
244 $this->gaps = array();
245 while ($data = $ilDB->fetchAssoc($result)) {
246 switch ($data["cloze_type"]) {
247 case CLOZE_TEXT:
248 if (!array_key_exists($data["gap_id"], $this->gaps)) {
249 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
250 }
251 $answer = new assAnswerCloze(
252 $data["answertext"],
253 $data["points"],
254 $data["aorder"]
255 );
256 $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
257
258 $this->gaps[$data["gap_id"]]->addItem($answer);
259 break;
260 case CLOZE_SELECT:
261 if (!array_key_exists($data["gap_id"], $this->gaps)) {
262 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
263 $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
264 }
265 $answer = new assAnswerCloze(
266 $data["answertext"],
267 $data["points"],
268 $data["aorder"]
269 );
270 $this->gaps[$data["gap_id"]]->addItem($answer);
271 break;
272 case CLOZE_NUMERIC:
273 if (!array_key_exists($data["gap_id"], $this->gaps)) {
274 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
275 }
276 $answer = new assAnswerCloze(
277 $data["answertext"],
278 $data["points"],
279 $data["aorder"]
280 );
281 $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
282 $answer->setLowerBound($data["lowerlimit"]);
283 $answer->setUpperBound($data["upperlimit"]);
284 $this->gaps[$data["gap_id"]]->addItem($answer);
285 break;
286 }
287 }
288 }
289 }
290 $assClozeGapCombinationObj = new assClozeGapCombination();
291 $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
292 if (count($check_for_gap_combinations) != 0) {
293 $this->setGapCombinationsExists(true);
294 $this->setGapCombinations($check_for_gap_combinations);
295 }
296 parent::loadFromDb($question_id);
297 }
298
299 #region Save question to db
300
310 public function saveToDb($original_id = "")
311 {
315
316 parent::saveToDb($original_id);
317 }
318
323 {
324 global $DIC;
325 $ilDB = $DIC['ilDB'];
326
327 $ilDB->manipulateF(
328 "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
329 array( "integer" ),
330 array( $this->getId() )
331 );
332
333 foreach ($this->gaps as $key => $gap) {
334 $this->saveClozeGapItemsToDb($gap, $key);
335 }
336 }
337
344 {
345 global $DIC; /* @var ILIAS\DI\Container $DIC */
346
347
348 $DIC->database()->manipulateF(
349 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
350 array( "integer" ),
351 array( $this->getId() )
352 );
353
354 $DIC->database()->insert($this->getAdditionalTableName(), array(
355 'question_fi' => array('integer', $this->getId()),
356 'textgap_rating' => array('text', $this->getTextgapRating()),
357 'identical_scoring' => array('text', $this->getIdenticalScoring()),
358 'fixed_textlen' => array('integer', $this->getFixedTextLength() ? $this->getFixedTextLength() : null),
359 'cloze_text' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0)),
360 'feedback_mode' => array('text', $this->getFeedbackMode())
361 ));
362 }
363
370 protected function saveClozeGapItemsToDb($gap, $key)
371 {
372 global $DIC;
373 $ilDB = $DIC['ilDB'];
374 foreach ($gap->getItems($this->getShuffler()) as $item) {
375 $query = "";
376 $next_id = $ilDB->nextId('qpl_a_cloze');
377 switch ($gap->getType()) {
378 case CLOZE_TEXT:
379 $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
380 break;
381 case CLOZE_SELECT:
382 $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
383 break;
384 case CLOZE_NUMERIC:
385 $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
386 break;
387 }
388 }
389 }
390
399 protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
400 {
401 global $DIC;
402 $ilDB = $DIC['ilDB'];
403 $ilDB->manipulateF(
404 "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)",
405 array(
406 "integer",
407 "integer",
408 "integer",
409 "text",
410 "float",
411 "integer",
412 "text",
413 "integer"
414 ),
415 array(
416 $next_id,
417 $this->getId(),
418 $key,
419 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
420 $item->getPoints(),
421 $item->getOrder(),
422 $gap->getType(),
423 (int) $gap->getGapSize()
424 )
425 );
426 }
427
436 protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
437 {
438 global $DIC;
439 $ilDB = $DIC['ilDB'];
440 $ilDB->manipulateF(
441 "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)",
442 array(
443 "integer",
444 "integer",
445 "integer",
446 "text",
447 "float",
448 "integer",
449 "text",
450 "text"
451 ),
452 array(
453 $next_id,
454 $this->getId(),
455 $key,
456 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
457 $item->getPoints(),
458 $item->getOrder(),
459 $gap->getType(),
460 ($gap->getShuffle()) ? "1" : "0"
461 )
462 );
463 }
464
473 protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
474 {
475 global $DIC;
476 $ilDB = $DIC['ilDB'];
477
478 include_once "./Services/Math/classes/class.EvalMath.php";
479 $eval = new EvalMath();
480 $eval->suppress_errors = true;
481 $ilDB->manipulateF(
482 "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)",
483 array(
484 "integer",
485 "integer",
486 "integer",
487 "text",
488 "float",
489 "integer",
490 "text",
491 "text",
492 "text",
493 "integer"
494 ),
495 array(
496 $next_id,
497 $this->getId(),
498 $key,
499 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
500 $item->getPoints(),
501 $item->getOrder(),
502 $gap->getType(),
503 ($eval->e($item->getLowerBound() !== false) && strlen(
504 $item->getLowerBound()
505 ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
506 ($eval->e($item->getUpperBound() !== false) && strlen(
507 $item->getUpperBound()
508 ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
509 (int) $gap->getGapSize()
510 )
511 );
512 }
513
514
515
516 #endregion Save question to db
517
524 public function getGaps()
525 {
526 return $this->gaps;
527 }
528
529
536 public function flushGaps()
537 {
538 $this->gaps = array();
539 }
540
550 public function setClozeText($cloze_text = "")
551 {
552 $this->gaps = array();
554 $this->cloze_text = $cloze_text;
556 }
557
558 public function setClozeTextValue($cloze_text = "")
559 {
560 $this->cloze_text = $cloze_text;
561 }
562
570 public function getClozeText()
571 {
572 return $this->cloze_text;
573 }
574
582 public function getStartTag()
583 {
584 return $this->start_tag;
585 }
586
594 public function setStartTag($start_tag = "[gap]")
595 {
596 $this->start_tag = $start_tag;
597 }
598
606 public function getEndTag()
607 {
608 return $this->end_tag;
609 }
610
618 public function setEndTag($end_tag = "[/gap]")
619 {
620 $this->end_tag = $end_tag;
621 }
622
626 public function getFeedbackMode()
627 {
628 return $this->feedbackMode;
629 }
630
635 {
636 $this->feedbackMode = $feedbackMode;
637 }
638
646 {
647 include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
648 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
649 $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
650 preg_match_all($search_pattern, $this->getClozeText(), $found);
651 $this->gaps = array();
652 if (count($found[0])) {
653 foreach ($found[1] as $gap_index => $answers) {
654 // create text gaps by default
655 $gap = new assClozeGap(CLOZE_TEXT);
656 $textparams = preg_split("/(?<!\\\\),/", $answers);
657 foreach ($textparams as $key => $value) {
658 $answer = new assAnswerCloze($value, 0, $key);
659 $gap->addItem($answer);
660 }
661 $this->gaps[$gap_index] = $gap;
662 }
663 }
664 }
665
671 public function setGapType($gap_index, $gap_type)
672 {
673 if (array_key_exists($gap_index, $this->gaps)) {
674 $this->gaps[$gap_index]->setType($gap_type);
675 }
676 }
677
687 public function setGapShuffle($gap_index = 0, $shuffle = 1)
688 {
689 if (array_key_exists($gap_index, $this->gaps)) {
690 $this->gaps[$gap_index]->setShuffle($shuffle);
691 }
692 }
693
700 public function clearGapAnswers()
701 {
702 foreach ($this->gaps as $gap_index => $gap) {
703 $this->gaps[$gap_index]->clearItems();
704 }
705 }
706
714 public function getGapCount()
715 {
716 if (is_array($this->gaps)) {
717 return count($this->gaps);
718 } else {
719 return 0;
720 }
721 }
722
733 public function addGapAnswer($gap_index, $order, $answer)
734 {
735 if (array_key_exists($gap_index, $this->gaps)) {
736 if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC) {
737 // only allow notation with "." for real numbers
738 $answer = str_replace(",", ".", $answer);
739 }
740 $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
741 }
742 }
743
752 public function getGap($gap_index = 0)
753 {
754 if (array_key_exists($gap_index, $this->gaps)) {
755 return $this->gaps[$gap_index];
756 } else {
757 return null;
758 }
759 }
760
761 public function setGapSize($gap_index, $order, $size)
762 {
763 if (array_key_exists($gap_index, $this->gaps)) {
764 $this->gaps[$gap_index]->setGapSize($size);
765 }
766 }
767
778 public function setGapAnswerPoints($gap_index, $order, $points)
779 {
780 if (array_key_exists($gap_index, $this->gaps)) {
781 $this->gaps[$gap_index]->setItemPoints($order, $points);
782 }
783 }
784
793 public function addGapText($gap_index)
794 {
795 if (array_key_exists($gap_index, $this->gaps)) {
796 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
797 $answer = new assAnswerCloze(
798 "",
799 0,
800 $this->gaps[$gap_index]->getItemCount()
801 );
802 $this->gaps[$gap_index]->addItem($answer);
803 }
804 }
805
814 public function addGapAtIndex($gap, $index)
815 {
816 $this->gaps[$index] = $gap;
817 }
818
829 public function setGapAnswerLowerBound($gap_index, $order, $bound)
830 {
831 if (array_key_exists($gap_index, $this->gaps)) {
832 $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
833 }
834 }
835
846 public function setGapAnswerUpperBound($gap_index, $order, $bound)
847 {
848 if (array_key_exists($gap_index, $this->gaps)) {
849 $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
850 }
851 }
852
859 public function getMaximumPoints()
860 {
861 $assClozeGapCombinationObj = new assClozeGapCombination();
862 $points = 0;
863 $gaps_used_in_combination = array();
864 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
865 $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
866 $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
867 }
868 foreach ($this->gaps as $gap_index => $gap) {
869 if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
870 if ($gap->getType() == CLOZE_TEXT) {
871 $gap_max_points = 0;
872 foreach ($gap->getItems($this->getShuffler()) as $item) {
873 if ($item->getPoints() > $gap_max_points) {
874 $gap_max_points = $item->getPoints();
875 }
876 }
877 $points += $gap_max_points;
878 } elseif ($gap->getType() == CLOZE_SELECT) {
879 $srpoints = 0;
880 foreach ($gap->getItems($this->getShuffler()) as $item) {
881 if ($item->getPoints() > $srpoints) {
882 $srpoints = $item->getPoints();
883 }
884 }
885 $points += $srpoints;
886 } elseif ($gap->getType() == CLOZE_NUMERIC) {
887 $numpoints = 0;
888 foreach ($gap->getItems($this->getShuffler()) as $item) {
889 if ($item->getPoints() > $numpoints) {
890 $numpoints = $item->getPoints();
891 }
892 }
893 $points += $numpoints;
894 }
895 }
896 }
897
898 return $points;
899 }
900
906 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
907 {
908 if ($this->id <= 0) {
909 // The question has not been saved. It cannot be duplicated
910 return;
911 }
912 // duplicate the question in database
913 $this_id = $this->getId();
914 $thisObjId = $this->getObjId();
915
916 $clone = $this;
917 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
919 $clone->id = -1;
920
921 if ((int) $testObjId > 0) {
922 $clone->setObjId($testObjId);
923 }
924
925 if ($title) {
926 $clone->setTitle($title);
927 }
928 if ($author) {
929 $clone->setAuthor($author);
930 }
931 if ($owner) {
932 $clone->setOwner($owner);
933 }
934 if ($for_test) {
935 $clone->saveToDb($original_id);
936 } else {
937 $clone->saveToDb();
938 }
939 if ($this->gap_combinations_exists) {
940 $this->copyGapCombination($this_id, $clone->getId());
941 }
942 if ($for_test) {
943 $clone->saveToDb($original_id);
944 } else {
945 $clone->saveToDb();
946 }
947 // copy question page content
948 $clone->copyPageOfQuestion($this_id);
949 // copy XHTML media objects
950 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
951
952 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
953
954 return $clone->getId();
955 }
956
962 public function copyObject($target_questionpool_id, $title = "")
963 {
964 if ($this->getId() <= 0) {
965 // The question has not been saved. It cannot be duplicated
966 return;
967 }
968
969 $thisId = $this->getId();
970 $thisObjId = $this->getObjId();
971
972 $clone = $this;
973 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
975 $clone->id = -1;
976 $clone->setObjId($target_questionpool_id);
977 if ($title) {
978 $clone->setTitle($title);
979 }
980
981 $clone->saveToDb();
982
983 if ($this->gap_combinations_exists) {
984 $this->copyGapCombination($original_id, $clone->getId());
985 $clone->saveToDb();
986 }
987
988 // copy question page content
989 $clone->copyPageOfQuestion($original_id);
990 // copy XHTML media objects
991 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
992
993 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
994
995 return $clone->getId();
996 }
997
998 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
999 {
1000 if ($this->id <= 0) {
1001 // The question has not been saved. It cannot be duplicated
1002 return;
1003 }
1004
1005 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
1006
1007 $sourceQuestionId = $this->id;
1008 $sourceParentId = $this->getObjId();
1009
1010 // duplicate the question in database
1011 $clone = $this;
1012 $clone->id = -1;
1013
1014 $clone->setObjId($targetParentId);
1015
1016 if ($targetQuestionTitle) {
1017 $clone->setTitle($targetQuestionTitle);
1018 }
1019
1020 $clone->saveToDb();
1021
1022 if ($this->gap_combinations_exists) {
1023 $this->copyGapCombination($sourceQuestionId, $clone->getId());
1024 $clone->saveToDb();
1025 }
1026 // copy question page content
1027 $clone->copyPageOfQuestion($sourceQuestionId);
1028 // copy XHTML media objects
1029 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
1030
1031 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
1032
1033 return $clone->id;
1034 }
1035
1036 public function copyGapCombination($orgID, $newID)
1037 {
1038 $assClozeGapCombinationObj = new assClozeGapCombination();
1039 $array = $assClozeGapCombinationObj->loadFromDb($orgID);
1040 $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array);
1041 }
1042
1048 public function updateClozeTextFromGaps()
1049 {
1050 $output = $this->getClozeText();
1051 foreach ($this->getGaps() as $gap_index => $gap) {
1052 $answers = array();
1053 foreach ($gap->getItemsRaw() as $item) {
1054 array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1055 }
1056 // fau: fixGapReplace - use replace function
1057 $output = $this->replaceFirstGap($output, "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]");
1058// fau.
1059 }
1060 $output = str_replace("_gap]", "gap]", $output);
1061 $this->cloze_text = $output;
1062 }
1063
1073 public function deleteAnswerText($gap_index, $answer_index)
1074 {
1075 if (array_key_exists($gap_index, $this->gaps)) {
1076 if ($this->gaps[$gap_index]->getItemCount() == 1) {
1077 // this is the last answer text => remove the gap
1078 $this->deleteGap($gap_index);
1079 } else {
1080 // remove the answer text
1081 $this->gaps[$gap_index]->deleteItem($answer_index);
1082 $this->updateClozeTextFromGaps();
1083 }
1084 }
1085 }
1086
1095 public function deleteGap($gap_index)
1096 {
1097 if (array_key_exists($gap_index, $this->gaps)) {
1098 $output = $this->getClozeText();
1099 foreach ($this->getGaps() as $replace_gap_index => $gap) {
1100 $answers = array();
1101 foreach ($gap->getItemsRaw() as $item) {
1102 array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1103 }
1104 if ($replace_gap_index == $gap_index) {
1105 // fau: fixGapReplace - use replace function
1106 $output = $this->replaceFirstGap($output, '');
1107// fau.
1108 } else {
1109 // fau: fixGapReplace - use replace function
1110 $output = $this->replaceFirstGap($output, "[_gap]" . join(",", $answers) . "[/_gap]");
1111// fau.
1112 }
1113 }
1114 $output = str_replace("_gap]", "gap]", $output);
1115 $this->cloze_text = $output;
1116 unset($this->gaps[$gap_index]);
1117 $this->gaps = array_values($this->gaps);
1118 }
1119 }
1120
1130 public function getTextgapPoints($a_original, $a_entered, $max_points)
1131 {
1132 include_once "./Services/Utilities/classes/class.ilStr.php";
1133 $result = 0;
1134 $gaprating = $this->getTextgapRating();
1135 switch ($gaprating) {
1137 if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) {
1138 $result = $max_points;
1139 }
1140 break;
1142 if (strcmp($a_original, $a_entered) == 0) {
1143 $result = $max_points;
1144 }
1145 break;
1147 if (levenshtein($a_original, $a_entered) <= 1) {
1148 $result = $max_points;
1149 }
1150 break;
1152 if (levenshtein($a_original, $a_entered) <= 2) {
1153 $result = $max_points;
1154 }
1155 break;
1157 if (levenshtein($a_original, $a_entered) <= 3) {
1158 $result = $max_points;
1159 }
1160 break;
1162 if (levenshtein($a_original, $a_entered) <= 4) {
1163 $result = $max_points;
1164 }
1165 break;
1167 if (levenshtein($a_original, $a_entered) <= 5) {
1168 $result = $max_points;
1169 }
1170 break;
1171 }
1172 return $result;
1173 }
1174
1184 public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1185 {
1186 // fau: fixGapFormula - check entered value by evalMath
1187 // if( ! $this->checkForValidFormula($a_entered) )
1188 // {
1189 // return 0;
1190 // }
1191
1192 include_once "./Services/Math/classes/class.EvalMath.php";
1193 $eval = new EvalMath();
1194 $eval->suppress_errors = true;
1195 $result = 0;
1196
1197 if ($eval->e($a_entered) === false) {
1198 return 0;
1199 } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) {
1200 // fau.
1201 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1202 $result = $max_points;
1203 }
1204 } elseif ($eval->e($lowerBound) !== false) {
1205 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
1206 $result = $max_points;
1207 }
1208 } elseif ($eval->e($upperBound) !== false) {
1209 if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1210 $result = $max_points;
1211 }
1212 } else {
1213 if ($eval->e($a_entered) == $eval->e($a_original)) {
1214 $result = $max_points;
1215 }
1216 }
1217 return $result;
1218 }
1219
1224 public function checkForValidFormula($value)
1225 {
1226 return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1227 }
1238 public function calculateReachedPoints($active_id, $pass = null, $authorized = true, $returndetails = false)
1239 {
1240 global $DIC;
1241 $ilDB = $DIC['ilDB'];
1242
1243 if (is_null($pass)) {
1244 $pass = $this->getSolutionMaxPass($active_id);
1245 }
1246
1247 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1248 $user_result = array();
1249 while ($data = $ilDB->fetchAssoc($result)) {
1250 if (strcmp($data["value2"], "") != 0) {
1251 $user_result[$data["value1"]] = array(
1252 "gap_id" => $data["value1"],
1253 "value" => $data["value2"]
1254 );
1255 }
1256 }
1257
1258 ksort($user_result); // this is required when identical scoring for same solutions is disabled
1259
1260 if ($returndetails) {
1261 $detailed = array();
1262 $this->calculateReachedPointsForSolution($user_result, $detailed);
1263 return $detailed;
1264 }
1265
1266 return $this->calculateReachedPointsForSolution($user_result);
1267 }
1268
1269 protected function isValidNumericSubmitValue($submittedValue)
1270 {
1271 if (is_numeric($submittedValue)) {
1272 return true;
1273 }
1274
1275 if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1276 return true;
1277 }
1278
1279 return false;
1280 }
1281
1282 public function validateSolutionSubmit()
1283 {
1284 foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) {
1285 $gap = $this->getGap($gapIndex);
1286
1287 if ($gap->getType() != CLOZE_NUMERIC) {
1288 continue;
1289 }
1290
1291 if (strlen($value) && !$this->isValidNumericSubmitValue($value)) {
1292 ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1293 return false;
1294 }
1295 }
1296
1297 return true;
1298 }
1299
1300 public function fetchSolutionSubmit($submit)
1301 {
1302 $solutionSubmit = array();
1303
1304 foreach ($submit as $key => $value) {
1305 if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1306 $value = ilUtil::stripSlashes($value, false);
1307 if (strlen($value)) {
1308 $gap = $this->getGap($matches[1]);
1309 if (is_object($gap)) {
1310 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1311 if ($gap->getType() == CLOZE_NUMERIC && !is_numeric(str_replace(",", ".", $value))) {
1312 $value = null;
1313 } else if ($gap->getType() == CLOZE_NUMERIC) {
1314 $value = str_replace(",", ".", $value);
1315 }
1316 $solutionSubmit[trim($matches[1])] = $value;
1317 }
1318 }
1319 }
1320 }
1321 }
1322
1323 return $solutionSubmit;
1324 }
1325
1327 {
1328 $submit = $_POST;
1329 $solutionSubmit = array();
1330
1331 foreach ($submit as $key => $value) {
1332 if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1333 $value = ilUtil::stripSlashes($value, false);
1334 if (strlen($value)) {
1335 $gap = $this->getGap($matches[1]);
1336 if (is_object($gap)) {
1337 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1338 if ($gap->getType() == CLOZE_NUMERIC) {
1339 $value = str_replace(",", ".", $value);
1340 }
1341 $solutionSubmit[trim($matches[1])] = $value;
1342 }
1343 }
1344 }
1345 }
1346 }
1347
1348 return $solutionSubmit;
1349 }
1350
1351 public function getSolutionSubmit()
1352 {
1353 return $this->fetchSolutionSubmit($_POST);
1354 }
1355
1364 public function saveWorkingData($active_id, $pass = null, $authorized = true)
1365 {
1366 global $DIC;
1367 $ilDB = $DIC['ilDB'];
1368 $ilUser = $DIC['ilUser'];
1369 if (is_null($pass)) {
1370 include_once "./Modules/Test/classes/class.ilObjTest.php";
1371 $pass = ilObjTest::_getPass($active_id);
1372 }
1373
1374 $entered_values = 0;
1375
1376 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
1377 $this->removeCurrentSolution($active_id, $pass, $authorized);
1378
1379 foreach ($this->getSolutionSubmit() as $val1 => $val2) {
1380 $value = trim(ilUtil::stripSlashes($val2, false));
1381 if (strlen($value)) {
1382 $gap = $this->getGap(trim(ilUtil::stripSlashes($val1)));
1383 if (is_object($gap)) {
1384 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1385 $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized);
1386 $entered_values++;
1387 }
1388 }
1389 }
1390 }
1391 });
1392
1393 if ($entered_values) {
1394 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1396 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1397 }
1398 } else {
1399 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1401 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1402 }
1403 }
1404
1405 return true;
1406 }
1407
1414 public function getQuestionType()
1415 {
1416 return "assClozeTest";
1417 }
1418
1426 public function getTextgapRating()
1427 {
1428 return $this->textgap_rating;
1429 }
1430
1438 public function setTextgapRating($a_textgap_rating)
1439 {
1440 switch ($a_textgap_rating) {
1448 $this->textgap_rating = $a_textgap_rating;
1449 break;
1450 default:
1451 $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1452 break;
1453 }
1454 }
1455
1463 public function getIdenticalScoring()
1464 {
1465 return ($this->identical_scoring) ? 1 : 0;
1466 }
1467
1475 public function setIdenticalScoring($a_identical_scoring)
1476 {
1477 $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1478 }
1479
1486 public function getAdditionalTableName()
1487 {
1488 return "qpl_qst_cloze";
1489 }
1490
1497 public function getAnswerTableName()
1498 {
1499 return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1500 }
1501
1508 public function setFixedTextLength($a_text_len)
1509 {
1510 $this->fixedTextLength = $a_text_len;
1511 }
1512
1519 public function getFixedTextLength()
1520 {
1522 }
1523
1532 public function getMaximumGapPoints($gap_index)
1533 {
1534 $points = 0;
1535 $gap_max_points = 0;
1536 if (array_key_exists($gap_index, $this->gaps)) {
1537 $gap = &$this->gaps[$gap_index];
1538 foreach ($gap->getItems($this->getShuffler()) as $answer) {
1539 if ($answer->getPoints() > $gap_max_points) {
1540 $gap_max_points = $answer->getPoints();
1541 }
1542 }
1543 $points += $gap_max_points;
1544 }
1545 return $points;
1546 }
1547
1553 {
1554 return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1555 }
1557 {
1559 }
1560
1561 public function getGapCombinations()
1562 {
1564 }
1565
1566 public function setGapCombinationsExists($value)
1567 {
1568 $this->gap_combinations_exists = $value;
1569 }
1570
1571 public function setGapCombinations($value)
1572 {
1573 $this->gap_combinations = $value;
1574 }
1575
1579 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1580 {
1581 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1582
1583 $solution = $this->getSolutionValues($active_id, $pass);
1584 $i = 1;
1585 foreach ($this->getGaps() as $gap_index => $gap) {
1586 $worksheet->setCell($startrow + $i, 0, $this->lng->txt("gap") . " $i");
1587 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1588 $checked = false;
1589 foreach ($solution as $solutionvalue) {
1590 if ($gap_index == $solutionvalue["value1"]) {
1591 $string_escaping_org_value = $worksheet->getStringEscaping();
1592 try {
1593 $worksheet->setStringEscaping(false);
1594
1595 switch ($gap->getType()) {
1596 case CLOZE_SELECT:
1597 $worksheet->setCell($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1598 break;
1599 case CLOZE_NUMERIC:
1600 case CLOZE_TEXT:
1601 $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1602 break;
1603 }
1604 } finally {
1605 $worksheet->setStringEscaping($string_escaping_org_value);
1606 }
1607 }
1608 }
1609 $i++;
1610 }
1611
1612 return $startrow + $i + 1;
1613 }
1614
1619 {
1620 // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1621 //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1622 $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1623 // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1624 }
1625
1629 public function toJSON()
1630 {
1631 include_once("./Services/RTE/classes/class.ilRTE.php");
1632 $result = array();
1633 $result['id'] = (int) $this->getId();
1634 $result['type'] = (string) $this->getQuestionType();
1635 $result['title'] = (string) $this->getTitle();
1636 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1637 $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1638 $result['nr_of_tries'] = (int) $this->getNrOfTries();
1639 $result['shuffle'] = (bool) $this->getShuffle();
1640 $result['feedback'] = array(
1641 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1642 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1643 );
1644
1645 $gaps = array();
1646 foreach ($this->getGaps() as $key => $gap) {
1647 $items = array();
1648 foreach ($gap->getItems($this->getShuffler()) as $item) {
1649 $jitem = array();
1650 $jitem['points'] = $item->getPoints();
1651 $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1652 $jitem['order'] = $item->getOrder();
1653 if ($gap->getType() == CLOZE_NUMERIC) {
1654 $jitem['lowerbound'] = $item->getLowerBound();
1655 $jitem['upperbound'] = $item->getUpperBound();
1656 } else {
1657 $jitem['value'] = trim($jitem['value']);
1658 }
1659 array_push($items, $jitem);
1660 }
1661
1662 if ($gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC)) {
1663 $jgap['size'] = $gap->getGapSize();
1664 }
1665
1666 $jgap['shuffle'] = $gap->getShuffle();
1667 $jgap['type'] = $gap->getType();
1668 $jgap['item'] = $items;
1669
1670 array_push($gaps, $jgap);
1671 }
1672 $result['gaps'] = $gaps;
1673 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1674 $result['mobs'] = $mobs;
1675 return json_encode($result);
1676 }
1677
1686 public function getOperators($expression)
1687 {
1688 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1690 }
1691
1696 public function getExpressionTypes()
1697 {
1698 return array(
1704 );
1705 }
1706
1715 public function getUserQuestionResult($active_id, $pass)
1716 {
1718 global $DIC;
1719 $ilDB = $DIC['ilDB'];
1720 $result = new ilUserQuestionResult($this, $active_id, $pass);
1721
1722 $maxStep = $this->lookupMaxStep($active_id, $pass);
1723
1724 if ($maxStep !== null) {
1725 $data = $ilDB->queryF(
1726 "
1727 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1728 FROM tst_solutions sol
1729 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1730 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1731 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1732 ",
1733 array("integer", "integer", "integer","integer"),
1734 array($active_id, $pass, $this->getId(), $maxStep)
1735 );
1736 } else {
1737 $data = $ilDB->queryF(
1738 "
1739 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1740 FROM tst_solutions sol
1741 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1742 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1743 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1744 ",
1745 array("integer", "integer", "integer"),
1746 array($active_id, $pass, $this->getId())
1747 );
1748 }
1749
1750 while ($row = $ilDB->fetchAssoc($data)) {
1751 if ($row["cloze_type"] == 1) {
1752 $row["value2"]++;
1753 }
1754 $result->addKeyValue($row["val"], $row["value2"]);
1755 }
1756
1757 $points = $this->calculateReachedPoints($active_id, $pass);
1758 $max_points = $this->getMaximumPoints();
1759
1760 $result->setReachedPercentage(($points / $max_points) * 100);
1761
1762 return $result;
1763 }
1764
1773 public function getAvailableAnswerOptions($index = null)
1774 {
1775 if ($index !== null) {
1776 return $this->getGap($index);
1777 } else {
1778 return $this->getGaps();
1779 }
1780 }
1781
1782 public function calculateCombinationResult($user_result)
1783 {
1784 $points = 0;
1785
1786 $assClozeGapCombinationObj = new assClozeGapCombination();
1787
1788 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1789 $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1790 $gap_answers = array();
1791 $gap_used_in_combination = array();
1792 foreach ($user_result as $user_result_build_list) {
1793 if (is_array($user_result_build_list)) {
1794 $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1795 }
1796 }
1797
1798 foreach ($combinations_for_question as $combination) {
1799 foreach ($combination as $row_key => $row_answers) {
1800 $combination_fulfilled = true;
1801 $points_for_combination = $row_answers['points'];
1802 foreach ($row_answers as $gap_key => $combination_gap_answer) {
1803 if ($gap_key !== 'points') {
1804 $gap_used_in_combination[$gap_key] = $gap_key;
1805 }
1806 if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1807 switch ($combination_gap_answer['type']) {
1808 case CLOZE_TEXT:
1809 $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1810 if ($is_text_gap_correct != 1) {
1811 $combination_fulfilled = false;
1812 }
1813 break;
1814 case CLOZE_SELECT:
1815 $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1816 $answertext = $answer->getAnswertext();
1817 if ($answertext != $combination_gap_answer['answer']) {
1818 $combination_fulfilled = false;
1819 }
1820 break;
1821 case CLOZE_NUMERIC:
1822 $answer = $this->gaps[$gap_key]->getItem(0);
1823 if ($combination_gap_answer['answer'] != 'out_of_bound') {
1824 $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1825 if ($is_numeric_gap_correct != 1) {
1826 $combination_fulfilled = false;
1827 }
1828 } else {
1829 $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1830 if ($wrong_is_the_new_right == 1) {
1831 $combination_fulfilled = false;
1832 }
1833 }
1834 break;
1835 }
1836 } else {
1837 if ($gap_key !== 'points') {
1838 $combination_fulfilled = false;
1839 }
1840 }
1841 }
1842 if ($combination_fulfilled) {
1843 $points += $points_for_combination;
1844 }
1845 }
1846 }
1847 }
1848 return array($points, $gap_used_in_combination);
1849 }
1855 protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1856 {
1857 if ($detailed === null) {
1858 $detailed = array();
1859 }
1860
1861 $assClozeGapCombinationObj = new assClozeGapCombination();
1862 $combinations[1] = array();
1863 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1864 $combinations = $this->calculateCombinationResult($user_result);
1865 $points = $combinations[0];
1866 }
1867 $counter = 0;
1868 $solution_values_text = array(); // for identical scoring checks
1869 $solution_values_select = array(); // for identical scoring checks
1870 $solution_values_numeric = array(); // for identical scoring checks
1871 foreach ($user_result as $gap_id => $value) {
1872 if (is_string($value)) {
1873 $value = array("value" => $value);
1874 }
1875
1876 if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1877 switch ($this->gaps[$gap_id]->getType()) {
1878 case CLOZE_TEXT:
1879 $gappoints = 0;
1880 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1881 $answer = $this->gaps[$gap_id]->getItem($order);
1882 $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1883 if ($gotpoints > $gappoints) {
1884 $gappoints = $gotpoints;
1885 }
1886 }
1887 if (!$this->getIdenticalScoring()) {
1888 // check if the same solution text was already entered
1889 if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0)) {
1890 $gappoints = 0;
1891 }
1892 }
1893 $points += $gappoints;
1894 $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1895 array_push($solution_values_text, $value["value"]);
1896 break;
1897 case CLOZE_NUMERIC:
1898 $gappoints = 0;
1899 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1900 $answer = $this->gaps[$gap_id]->getItem($order);
1901 $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1902 if ($gotpoints > $gappoints) {
1903 $gappoints = $gotpoints;
1904 }
1905 }
1906 if (!$this->getIdenticalScoring()) {
1907 // check if the same solution value was already entered
1908 include_once "./Services/Math/classes/class.EvalMath.php";
1909 $eval = new EvalMath();
1910 $eval->suppress_errors = true;
1911 $found_value = false;
1912 foreach ($solution_values_numeric as $solval) {
1913 if ($eval->e($solval) == $eval->e($value["value"])) {
1914 $found_value = true;
1915 }
1916 }
1917 if ($found_value && ($gappoints > 0)) {
1918 $gappoints = 0;
1919 }
1920 }
1921 $points += $gappoints;
1922 $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1923 array_push($solution_values_numeric, $value["value"]);
1924 break;
1925 case CLOZE_SELECT:
1926 if ($value["value"] >= 0) {
1927 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1928 $answer = $this->gaps[$gap_id]->getItem($order);
1929 if ($value["value"] == $answer->getOrder()) {
1930 $answerpoints = $answer->getPoints();
1931 if (!$this->getIdenticalScoring()) {
1932 // check if the same solution value was already entered
1933 if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0)) {
1934 $answerpoints = 0;
1935 }
1936 }
1937 $points += $answerpoints;
1938 $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? true : false, "positive" => ($answerpoints > 0) ? true : false);
1939 array_push($solution_values_select, $answer->getAnswertext());
1940 }
1941 }
1942 }
1943 break;
1944 }
1945 }
1946 }
1947
1948 return $points;
1949 }
1950
1952 {
1953 $userSolution = array();
1954
1955 foreach ($previewSession->getParticipantsSolution() as $key => $val) {
1956 $userSolution[$key] = array('gap_id' => $key, 'value' => $val);
1957 }
1958
1959 $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
1960 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
1961
1962 return $this->ensureNonNegativePoints($reachedPoints);
1963 }
1964
1965 public function fetchAnswerValueForGap($userSolution, $gapIndex)
1966 {
1967 $answerValue = '';
1968
1969 foreach ($userSolution as $value1 => $value2) {
1970 if ($value1 == $gapIndex) {
1971 $answerValue = $value2;
1972 break;
1973 }
1974 }
1975
1976 return $answerValue;
1977 }
1978
1979 public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
1980 {
1981 $gap = $this->getGap($qIndex);
1982
1983 if ($gap->getType() != CLOZE_TEXT) {
1984 return false;
1985 }
1986
1987 foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) {
1988 if ($item->getAnswertext() == $answerOptionValue) {
1989 return false;
1990 }
1991 }
1992
1993 return true;
1994 }
1995
1996 public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
1997 {
1998 $gap = $this->getGap($qIndex); /* @var assClozeGap $gap */
1999
2000 $item = new assAnswerCloze($answerOptionValue, $points);
2001 $item->setOrder($gap->getItemCount());
2002
2003 $gap->addItem($item);
2004 }
2005
2006 public function savePartial()
2007 {
2008 return true;
2009 }
2010}
$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.
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.
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...
static strToLower($a_string)
Definition: class.ilStr.php:87
Class ilUserQuestionResult.
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
static stripSlashes($a_str, $a_strip_html=true, $a_allow="")
strip slashes if magic qoutes is enabled
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
$mobs
$ilUser
Definition: imgupload.php:18
$DIC
Definition: xapitoken.php:46