ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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 ) {
124 parent::__construct($title, $comment, $author, $owner, $question);
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 // replacement of old syntax with new syntax
217 include_once("./Services/RTE/classes/class.ilRTE.php");
218 $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1);
219 $this->cloze_text = ilRTE::_replaceMediaObjectImageSrc($this->cloze_text, 1);
220 $this->setTextgapRating($data["textgap_rating"]);
221 $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
222
223 try {
224 $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
225 } catch (ilTestQuestionPoolException $e) {
226 }
227
228 // open the cloze gaps with all answers
229 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
230 include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
231 $result = $ilDB->queryF(
232 "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
233 array("integer"),
234 array($question_id)
235 );
236 if ($result->numRows() > 0) {
237 $this->gaps = array();
238 while ($data = $ilDB->fetchAssoc($result)) {
239 switch ($data["cloze_type"]) {
240 case CLOZE_TEXT:
241 if (!array_key_exists($data["gap_id"], $this->gaps)) {
242 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_TEXT);
243 }
244 $answer = new assAnswerCloze(
245 $data["answertext"],
246 $data["points"],
247 $data["aorder"]
248 );
249 $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
250
251 $this->gaps[$data["gap_id"]]->addItem($answer);
252 break;
253 case CLOZE_SELECT:
254 if (!array_key_exists($data["gap_id"], $this->gaps)) {
255 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_SELECT);
256 $this->gaps[$data["gap_id"]]->setShuffle($data["shuffle"]);
257 }
258 $answer = new assAnswerCloze(
259 $data["answertext"],
260 $data["points"],
261 $data["aorder"]
262 );
263 $this->gaps[$data["gap_id"]]->addItem($answer);
264 break;
265 case CLOZE_NUMERIC:
266 if (!array_key_exists($data["gap_id"], $this->gaps)) {
267 $this->gaps[$data["gap_id"]] = new assClozeGap(CLOZE_NUMERIC);
268 }
269 $answer = new assAnswerCloze(
270 $data["answertext"],
271 $data["points"],
272 $data["aorder"]
273 );
274 $this->gaps[$data["gap_id"]]->setGapSize($data['gap_size']);
275 $answer->setLowerBound($data["lowerlimit"]);
276 $answer->setUpperBound($data["upperlimit"]);
277 $this->gaps[$data["gap_id"]]->addItem($answer);
278 break;
279 }
280 }
281 }
282 }
283 $assClozeGapCombinationObj = new assClozeGapCombination();
284 $check_for_gap_combinations = $assClozeGapCombinationObj->loadFromDb($question_id);
285 if (count($check_for_gap_combinations) != 0) {
286 $this->setGapCombinationsExists(true);
287 $this->setGapCombinations($check_for_gap_combinations);
288 }
289 parent::loadFromDb($question_id);
290 }
291
292 #region Save question to db
293
303 public function saveToDb($original_id = "")
304 {
308
309 parent::saveToDb($original_id);
310 }
311
316 {
317 global $DIC;
318 $ilDB = $DIC['ilDB'];
319
320 $ilDB->manipulateF(
321 "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
322 array( "integer" ),
323 array( $this->getId() )
324 );
325
326 foreach ($this->gaps as $key => $gap) {
327 $this->saveClozeGapItemsToDb($gap, $key);
328 }
329 }
330
337 {
338 global $DIC; /* @var ILIAS\DI\Container $DIC */
339
340
341 $DIC->database()->manipulateF(
342 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
343 array( "integer" ),
344 array( $this->getId() )
345 );
346
347 $DIC->database()->insert($this->getAdditionalTableName(), array(
348 'question_fi' => array('integer', $this->getId()),
349 'textgap_rating' => array('text', $this->getTextgapRating()),
350 'identical_scoring' => array('text', $this->getIdenticalScoring()),
351 'fixed_textlen' => array('integer', $this->getFixedTextLength() ? $this->getFixedTextLength() : null),
352 'cloze_text' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0)),
353 'feedback_mode' => array('text', $this->getFeedbackMode())
354 ));
355 }
356
363 protected function saveClozeGapItemsToDb($gap, $key)
364 {
365 global $DIC;
366 $ilDB = $DIC['ilDB'];
367 foreach ($gap->getItems($this->getShuffler()) as $item) {
368 $query = "";
369 $next_id = $ilDB->nextId('qpl_a_cloze');
370 switch ($gap->getType()) {
371 case CLOZE_TEXT:
372 $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
373 break;
374 case CLOZE_SELECT:
375 $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
376 break;
377 case CLOZE_NUMERIC:
378 $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
379 break;
380 }
381 }
382 }
383
392 protected function saveClozeTextGapRecordToDb($next_id, $key, $item, $gap)
393 {
394 global $DIC;
395 $ilDB = $DIC['ilDB'];
396 $ilDB->manipulateF(
397 "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)",
398 array(
399 "integer",
400 "integer",
401 "integer",
402 "text",
403 "float",
404 "integer",
405 "text",
406 "integer"
407 ),
408 array(
409 $next_id,
410 $this->getId(),
411 $key,
412 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
413 $item->getPoints(),
414 $item->getOrder(),
415 $gap->getType(),
416 (int) $gap->getGapSize()
417 )
418 );
419 }
420
429 protected function saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap)
430 {
431 global $DIC;
432 $ilDB = $DIC['ilDB'];
433 $ilDB->manipulateF(
434 "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)",
435 array(
436 "integer",
437 "integer",
438 "integer",
439 "text",
440 "float",
441 "integer",
442 "text",
443 "text"
444 ),
445 array(
446 $next_id,
447 $this->getId(),
448 $key,
449 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
450 $item->getPoints(),
451 $item->getOrder(),
452 $gap->getType(),
453 ($gap->getShuffle()) ? "1" : "0"
454 )
455 );
456 }
457
466 protected function saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap)
467 {
468 global $DIC;
469 $ilDB = $DIC['ilDB'];
470
471 include_once "./Services/Math/classes/class.EvalMath.php";
472 $eval = new EvalMath();
473 $eval->suppress_errors = true;
474 $ilDB->manipulateF(
475 "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)",
476 array(
477 "integer",
478 "integer",
479 "integer",
480 "text",
481 "float",
482 "integer",
483 "text",
484 "text",
485 "text",
486 "integer"
487 ),
488 array(
489 $next_id,
490 $this->getId(),
491 $key,
492 strlen($item->getAnswertext()) ? $item->getAnswertext() : "",
493 $item->getPoints(),
494 $item->getOrder(),
495 $gap->getType(),
496 ($eval->e($item->getLowerBound() !== false) && strlen(
497 $item->getLowerBound()
498 ) > 0) ? $item->getLowerBound() : $item->getAnswertext(),
499 ($eval->e($item->getUpperBound() !== false) && strlen(
500 $item->getUpperBound()
501 ) > 0) ? $item->getUpperBound() : $item->getAnswertext(),
502 (int) $gap->getGapSize()
503 )
504 );
505 }
506
507
508
509 #endregion Save question to db
510
517 public function getGaps()
518 {
519 return $this->gaps;
520 }
521
522
529 public function flushGaps()
530 {
531 $this->gaps = array();
532 }
533
543 public function setClozeText($cloze_text = "")
544 {
545 $this->gaps = array();
547 $this->cloze_text = $cloze_text;
549 }
550
551 public function setClozeTextValue($cloze_text = "")
552 {
553 $this->cloze_text = $cloze_text;
554 }
555
563 public function getClozeText()
564 {
565 return $this->cloze_text;
566 }
567
575 public function getStartTag()
576 {
577 return $this->start_tag;
578 }
579
587 public function setStartTag($start_tag = "[gap]")
588 {
589 $this->start_tag = $start_tag;
590 }
591
599 public function getEndTag()
600 {
601 return $this->end_tag;
602 }
603
611 public function setEndTag($end_tag = "[/gap]")
612 {
613 $this->end_tag = $end_tag;
614 }
615
619 public function getFeedbackMode()
620 {
621 return $this->feedbackMode;
622 }
623
628 {
629 $this->feedbackMode = $feedbackMode;
630 }
631
639 {
640 include_once "./Modules/TestQuestionPool/classes/class.assClozeGap.php";
641 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
642 $search_pattern = "|\[gap\](.*?)\[/gap\]|i";
643 preg_match_all($search_pattern, $this->getClozeText(), $found);
644 $this->gaps = array();
645 if (count($found[0])) {
646 foreach ($found[1] as $gap_index => $answers) {
647 // create text gaps by default
648 $gap = new assClozeGap(CLOZE_TEXT);
649 $textparams = preg_split("/(?<!\\\\),/", $answers);
650 foreach ($textparams as $key => $value) {
651 $answer = new assAnswerCloze($value, 0, $key);
652 $gap->addItem($answer);
653 }
654 $this->gaps[$gap_index] = $gap;
655 }
656 }
657 }
658
664 public function setGapType($gap_index, $gap_type)
665 {
666 if (array_key_exists($gap_index, $this->gaps)) {
667 $this->gaps[$gap_index]->setType($gap_type);
668 }
669 }
670
680 public function setGapShuffle($gap_index = 0, $shuffle = 1)
681 {
682 if (array_key_exists($gap_index, $this->gaps)) {
683 $this->gaps[$gap_index]->setShuffle($shuffle);
684 }
685 }
686
693 public function clearGapAnswers()
694 {
695 foreach ($this->gaps as $gap_index => $gap) {
696 $this->gaps[$gap_index]->clearItems();
697 }
698 }
699
707 public function getGapCount()
708 {
709 if (is_array($this->gaps)) {
710 return count($this->gaps);
711 } else {
712 return 0;
713 }
714 }
715
726 public function addGapAnswer($gap_index, $order, $answer)
727 {
728 if (array_key_exists($gap_index, $this->gaps)) {
729 if ($this->gaps[$gap_index]->getType() == CLOZE_NUMERIC) {
730 // only allow notation with "." for real numbers
731 $answer = str_replace(",", ".", $answer);
732 }
733 $this->gaps[$gap_index]->addItem(new assAnswerCloze($answer, 0, $order));
734 }
735 }
736
745 public function getGap($gap_index = 0)
746 {
747 if (array_key_exists($gap_index, $this->gaps)) {
748 return $this->gaps[$gap_index];
749 } else {
750 return null;
751 }
752 }
753
754 public function setGapSize($gap_index, $order, $size)
755 {
756 if (array_key_exists($gap_index, $this->gaps)) {
757 $this->gaps[$gap_index]->setGapSize($size);
758 }
759 }
760
771 public function setGapAnswerPoints($gap_index, $order, $points)
772 {
773 if (array_key_exists($gap_index, $this->gaps)) {
774 $this->gaps[$gap_index]->setItemPoints($order, $points);
775 }
776 }
777
786 public function addGapText($gap_index)
787 {
788 if (array_key_exists($gap_index, $this->gaps)) {
789 include_once "./Modules/TestQuestionPool/classes/class.assAnswerCloze.php";
790 $answer = new assAnswerCloze(
791 "",
792 0,
793 $this->gaps[$gap_index]->getItemCount()
794 );
795 $this->gaps[$gap_index]->addItem($answer);
796 }
797 }
798
807 public function addGapAtIndex($gap, $index)
808 {
809 $this->gaps[$index] = $gap;
810 }
811
822 public function setGapAnswerLowerBound($gap_index, $order, $bound)
823 {
824 if (array_key_exists($gap_index, $this->gaps)) {
825 $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
826 }
827 }
828
839 public function setGapAnswerUpperBound($gap_index, $order, $bound)
840 {
841 if (array_key_exists($gap_index, $this->gaps)) {
842 $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
843 }
844 }
845
852 public function getMaximumPoints()
853 {
854 $assClozeGapCombinationObj = new assClozeGapCombination();
855 $points = 0;
856 $gaps_used_in_combination = array();
857 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
858 $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->getId());
859 $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->getId());
860 }
861 foreach ($this->gaps as $gap_index => $gap) {
862 if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
863 if ($gap->getType() == CLOZE_TEXT) {
864 $gap_max_points = 0;
865 foreach ($gap->getItems($this->getShuffler()) as $item) {
866 if ($item->getPoints() > $gap_max_points) {
867 $gap_max_points = $item->getPoints();
868 }
869 }
870 $points += $gap_max_points;
871 } elseif ($gap->getType() == CLOZE_SELECT) {
872 $srpoints = 0;
873 foreach ($gap->getItems($this->getShuffler()) as $item) {
874 if ($item->getPoints() > $srpoints) {
875 $srpoints = $item->getPoints();
876 }
877 }
878 $points += $srpoints;
879 } elseif ($gap->getType() == CLOZE_NUMERIC) {
880 $numpoints = 0;
881 foreach ($gap->getItems($this->getShuffler()) as $item) {
882 if ($item->getPoints() > $numpoints) {
883 $numpoints = $item->getPoints();
884 }
885 }
886 $points += $numpoints;
887 }
888 }
889 }
890
891 return $points;
892 }
893
899 public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
900 {
901 if ($this->id <= 0) {
902 // The question has not been saved. It cannot be duplicated
903 return;
904 }
905 // duplicate the question in database
906 $this_id = $this->getId();
907 $thisObjId = $this->getObjId();
908
909 $clone = $this;
910 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
912 $clone->id = -1;
913
914 if ((int) $testObjId > 0) {
915 $clone->setObjId($testObjId);
916 }
917
918 if ($title) {
919 $clone->setTitle($title);
920 }
921 if ($author) {
922 $clone->setAuthor($author);
923 }
924 if ($owner) {
925 $clone->setOwner($owner);
926 }
927 if ($for_test) {
928 $clone->saveToDb($original_id);
929 } else {
930 $clone->saveToDb();
931 }
932 if ($this->gap_combinations_exists) {
933 $this->copyGapCombination($this_id, $clone->getId());
934 }
935 if ($for_test) {
936 $clone->saveToDb($original_id);
937 } else {
938 $clone->saveToDb();
939 }
940 // copy question page content
941 $clone->copyPageOfQuestion($this_id);
942 // copy XHTML media objects
943 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
944
945 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
946
947 return $clone->getId();
948 }
949
955 public function copyObject($target_questionpool_id, $title = "")
956 {
957 if ($this->getId() <= 0) {
958 // The question has not been saved. It cannot be duplicated
959 return;
960 }
961
962 $thisId = $this->getId();
963 $thisObjId = $this->getObjId();
964
965 $clone = $this;
966 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
968 $clone->id = -1;
969 $clone->setObjId($target_questionpool_id);
970 if ($title) {
971 $clone->setTitle($title);
972 }
973
974 $clone->saveToDb();
975
976 if ($this->gap_combinations_exists) {
977 $this->copyGapCombination($original_id, $clone->getId());
978 $clone->saveToDb();
979 }
980
981 // copy question page content
982 $clone->copyPageOfQuestion($original_id);
983 // copy XHTML media objects
984 $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
985
986 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
987
988 return $clone->getId();
989 }
990
991 public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
992 {
993 if ($this->id <= 0) {
994 // The question has not been saved. It cannot be duplicated
995 return;
996 }
997
998 include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
999
1000 $sourceQuestionId = $this->id;
1001 $sourceParentId = $this->getObjId();
1002
1003 // duplicate the question in database
1004 $clone = $this;
1005 $clone->id = -1;
1006
1007 $clone->setObjId($targetParentId);
1008
1009 if ($targetQuestionTitle) {
1010 $clone->setTitle($targetQuestionTitle);
1011 }
1012
1013 $clone->saveToDb();
1014
1015 if ($this->gap_combinations_exists) {
1016 $this->copyGapCombination($sourceQuestionId, $clone->getId());
1017 $clone->saveToDb();
1018 }
1019 // copy question page content
1020 $clone->copyPageOfQuestion($sourceQuestionId);
1021 // copy XHTML media objects
1022 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
1023
1024 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
1025
1026 return $clone->id;
1027 }
1028
1029 public function copyGapCombination($orgID, $newID)
1030 {
1031 $assClozeGapCombinationObj = new assClozeGapCombination();
1032 $array = $assClozeGapCombinationObj->loadFromDb($orgID);
1033 $assClozeGapCombinationObj->importGapCombinationToDb($newID, $array);
1034 }
1035
1041 public function updateClozeTextFromGaps()
1042 {
1043 $output = $this->getClozeText();
1044 foreach ($this->getGaps() as $gap_index => $gap) {
1045 $answers = array();
1046 foreach ($gap->getItemsRaw() as $item) {
1047 array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1048 }
1049 // fau: fixGapReplace - use replace function
1050 $output = $this->replaceFirstGap($output, "[_gap]" . $this->prepareTextareaOutput(join(",", $answers), true) . "[/_gap]");
1051// fau.
1052 }
1053 $output = str_replace("_gap]", "gap]", $output);
1054 $this->cloze_text = $output;
1055 }
1056
1066 public function deleteAnswerText($gap_index, $answer_index)
1067 {
1068 if (array_key_exists($gap_index, $this->gaps)) {
1069 if ($this->gaps[$gap_index]->getItemCount() == 1) {
1070 // this is the last answer text => remove the gap
1071 $this->deleteGap($gap_index);
1072 } else {
1073 // remove the answer text
1074 $this->gaps[$gap_index]->deleteItem($answer_index);
1075 $this->updateClozeTextFromGaps();
1076 }
1077 }
1078 }
1079
1088 public function deleteGap($gap_index)
1089 {
1090 if (array_key_exists($gap_index, $this->gaps)) {
1091 $output = $this->getClozeText();
1092 foreach ($this->getGaps() as $replace_gap_index => $gap) {
1093 $answers = array();
1094 foreach ($gap->getItemsRaw() as $item) {
1095 array_push($answers, str_replace(",", "\\,", $item->getAnswerText()));
1096 }
1097 if ($replace_gap_index == $gap_index) {
1098 // fau: fixGapReplace - use replace function
1099 $output = $this->replaceFirstGap($output, '');
1100// fau.
1101 } else {
1102 // fau: fixGapReplace - use replace function
1103 $output = $this->replaceFirstGap($output, "[_gap]" . join(",", $answers) . "[/_gap]");
1104// fau.
1105 }
1106 }
1107 $output = str_replace("_gap]", "gap]", $output);
1108 $this->cloze_text = $output;
1109 unset($this->gaps[$gap_index]);
1110 $this->gaps = array_values($this->gaps);
1111 }
1112 }
1113
1123 public function getTextgapPoints($a_original, $a_entered, $max_points)
1124 {
1125 include_once "./Services/Utilities/classes/class.ilStr.php";
1126 $result = 0;
1127 $gaprating = $this->getTextgapRating();
1128 switch ($gaprating) {
1130 if (strcmp(ilStr::strToLower($a_original), ilStr::strToLower($a_entered)) == 0) {
1131 $result = $max_points;
1132 }
1133 break;
1135 if (strcmp($a_original, $a_entered) == 0) {
1136 $result = $max_points;
1137 }
1138 break;
1140 if (levenshtein($a_original, $a_entered) <= 1) {
1141 $result = $max_points;
1142 }
1143 break;
1145 if (levenshtein($a_original, $a_entered) <= 2) {
1146 $result = $max_points;
1147 }
1148 break;
1150 if (levenshtein($a_original, $a_entered) <= 3) {
1151 $result = $max_points;
1152 }
1153 break;
1155 if (levenshtein($a_original, $a_entered) <= 4) {
1156 $result = $max_points;
1157 }
1158 break;
1160 if (levenshtein($a_original, $a_entered) <= 5) {
1161 $result = $max_points;
1162 }
1163 break;
1164 }
1165 return $result;
1166 }
1167
1177 public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound)
1178 {
1179 // fau: fixGapFormula - check entered value by evalMath
1180 // if( ! $this->checkForValidFormula($a_entered) )
1181 // {
1182 // return 0;
1183 // }
1184
1185 include_once "./Services/Math/classes/class.EvalMath.php";
1186 $eval = new EvalMath();
1187 $eval->suppress_errors = true;
1188 $result = 0;
1189
1190 if ($eval->e($a_entered) === false) {
1191 return 0;
1192 } elseif (($eval->e($lowerBound) !== false) && ($eval->e($upperBound) !== false)) {
1193 // fau.
1194 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1195 $result = $max_points;
1196 }
1197 } elseif ($eval->e($lowerBound) !== false) {
1198 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
1199 $result = $max_points;
1200 }
1201 } elseif ($eval->e($upperBound) !== false) {
1202 if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
1203 $result = $max_points;
1204 }
1205 } else {
1206 if ($eval->e($a_entered) == $eval->e($a_original)) {
1207 $result = $max_points;
1208 }
1209 }
1210 return $result;
1211 }
1212
1217 public function checkForValidFormula($value)
1218 {
1219 return preg_match("/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
1220 }
1231 public function calculateReachedPoints($active_id, $pass = null, $authorized = true, $returndetails = false)
1232 {
1233 global $DIC;
1234 $ilDB = $DIC['ilDB'];
1235
1236 if (is_null($pass)) {
1237 $pass = $this->getSolutionMaxPass($active_id);
1238 }
1239
1240 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
1241 $user_result = array();
1242 while ($data = $ilDB->fetchAssoc($result)) {
1243 if (strcmp($data["value2"], "") != 0) {
1244 $user_result[$data["value1"]] = array(
1245 "gap_id" => $data["value1"],
1246 "value" => $data["value2"]
1247 );
1248 }
1249 }
1250
1251 ksort($user_result); // this is required when identical scoring for same solutions is disabled
1252
1253 if ($returndetails) {
1254 $detailed = array();
1255 $this->calculateReachedPointsForSolution($user_result, $detailed);
1256 return $detailed;
1257 }
1258
1259 return $this->calculateReachedPointsForSolution($user_result);
1260 }
1261
1262 protected function isValidNumericSubmitValue($submittedValue)
1263 {
1264 if (is_numeric($submittedValue)) {
1265 return true;
1266 }
1267
1268 if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1269 return true;
1270 }
1271
1272 return false;
1273 }
1274
1275 public function validateSolutionSubmit()
1276 {
1277 foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) {
1278 $gap = $this->getGap($gapIndex);
1279
1280 if ($gap->getType() != CLOZE_NUMERIC) {
1281 continue;
1282 }
1283
1284 if (strlen($value) && !$this->isValidNumericSubmitValue($value)) {
1285 ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1286 return false;
1287 }
1288 }
1289
1290 return true;
1291 }
1292
1293 public function fetchSolutionSubmit($submit)
1294 {
1295 $solutionSubmit = array();
1296
1297 foreach ($submit as $key => $value) {
1298 if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1299 $value = ilUtil::stripSlashes($value, false);
1300 if (strlen($value)) {
1301 $gap = $this->getGap($matches[1]);
1302 if (is_object($gap)) {
1303 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1304 if ($gap->getType() == CLOZE_NUMERIC && !is_numeric(str_replace(",", ".", $value))) {
1305 $value = null;
1306 } else if ($gap->getType() == CLOZE_NUMERIC) {
1307 $value = str_replace(",", ".", $value);
1308 }
1309 $solutionSubmit[trim($matches[1])] = $value;
1310 }
1311 }
1312 }
1313 }
1314 }
1315
1316 return $solutionSubmit;
1317 }
1318
1320 {
1321 $submit = $_POST;
1322 $solutionSubmit = array();
1323
1324 foreach ($submit as $key => $value) {
1325 if (preg_match("/^gap_(\d+)/", $key, $matches)) {
1326 $value = ilUtil::stripSlashes($value, false);
1327 if (strlen($value)) {
1328 $gap = $this->getGap($matches[1]);
1329 if (is_object($gap)) {
1330 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1331 if ($gap->getType() == CLOZE_NUMERIC) {
1332 $value = str_replace(",", ".", $value);
1333 }
1334 $solutionSubmit[trim($matches[1])] = $value;
1335 }
1336 }
1337 }
1338 }
1339 }
1340
1341 return $solutionSubmit;
1342 }
1343
1344 public function getSolutionSubmit()
1345 {
1346 return $this->fetchSolutionSubmit($_POST);
1347 }
1348
1357 public function saveWorkingData($active_id, $pass = null, $authorized = true)
1358 {
1359 global $DIC;
1360 $ilDB = $DIC['ilDB'];
1361 $ilUser = $DIC['ilUser'];
1362 if (is_null($pass)) {
1363 include_once "./Modules/Test/classes/class.ilObjTest.php";
1364 $pass = ilObjTest::_getPass($active_id);
1365 }
1366
1367 $entered_values = 0;
1368
1369 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $active_id, $pass, $authorized) {
1370 $this->removeCurrentSolution($active_id, $pass, $authorized);
1371
1372 foreach ($this->getSolutionSubmit() as $val1 => $val2) {
1373 $value = trim(ilUtil::stripSlashes($val2, false));
1374 if (strlen($value)) {
1375 $gap = $this->getGap(trim(ilUtil::stripSlashes($val1)));
1376 if (is_object($gap)) {
1377 if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) {
1378 $this->saveCurrentSolution($active_id, $pass, $val1, $value, $authorized);
1379 $entered_values++;
1380 }
1381 }
1382 }
1383 }
1384 });
1385
1386 if ($entered_values) {
1387 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1389 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1390 }
1391 } else {
1392 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1394 assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1395 }
1396 }
1397
1398 return true;
1399 }
1400
1407 public function getQuestionType()
1408 {
1409 return "assClozeTest";
1410 }
1411
1419 public function getTextgapRating()
1420 {
1421 return $this->textgap_rating;
1422 }
1423
1431 public function setTextgapRating($a_textgap_rating)
1432 {
1433 switch ($a_textgap_rating) {
1441 $this->textgap_rating = $a_textgap_rating;
1442 break;
1443 default:
1444 $this->textgap_rating = TEXTGAP_RATING_CASEINSENSITIVE;
1445 break;
1446 }
1447 }
1448
1456 public function getIdenticalScoring()
1457 {
1458 return ($this->identical_scoring) ? 1 : 0;
1459 }
1460
1468 public function setIdenticalScoring($a_identical_scoring)
1469 {
1470 $this->identical_scoring = ($a_identical_scoring) ? 1 : 0;
1471 }
1472
1479 public function getAdditionalTableName()
1480 {
1481 return "qpl_qst_cloze";
1482 }
1483
1490 public function getAnswerTableName()
1491 {
1492 return array("qpl_a_cloze",'qpl_a_cloze_combi_res');
1493 }
1494
1501 public function setFixedTextLength($a_text_len)
1502 {
1503 $this->fixedTextLength = $a_text_len;
1504 }
1505
1512 public function getFixedTextLength()
1513 {
1515 }
1516
1525 public function getMaximumGapPoints($gap_index)
1526 {
1527 $points = 0;
1528 $gap_max_points = 0;
1529 if (array_key_exists($gap_index, $this->gaps)) {
1530 $gap = &$this->gaps[$gap_index];
1531 foreach ($gap->getItems($this->getShuffler()) as $answer) {
1532 if ($answer->getPoints() > $gap_max_points) {
1533 $gap_max_points = $answer->getPoints();
1534 }
1535 }
1536 $points += $gap_max_points;
1537 }
1538 return $points;
1539 }
1540
1546 {
1547 return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1548 }
1550 {
1552 }
1553
1554 public function getGapCombinations()
1555 {
1557 }
1558
1559 public function setGapCombinationsExists($value)
1560 {
1561 $this->gap_combinations_exists = $value;
1562 }
1563
1564 public function setGapCombinations($value)
1565 {
1566 $this->gap_combinations = $value;
1567 }
1568
1572 public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1573 {
1574 parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1575
1576 $solution = $this->getSolutionValues($active_id, $pass);
1577 $i = 1;
1578 foreach ($this->getGaps() as $gap_index => $gap) {
1579 $worksheet->setCell($startrow + $i, 0, $this->lng->txt("gap") . " $i");
1580 $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1581 $checked = false;
1582 foreach ($solution as $solutionvalue) {
1583 if ($gap_index == $solutionvalue["value1"]) {
1584 $string_escaping_org_value = $worksheet->getStringEscaping();
1585 try {
1586 $worksheet->setStringEscaping(false);
1587
1588 switch ($gap->getType()) {
1589 case CLOZE_SELECT:
1590 $worksheet->setCell($startrow + $i, 1, $gap->getItem($solutionvalue["value2"])->getAnswertext());
1591 break;
1592 case CLOZE_NUMERIC:
1593 case CLOZE_TEXT:
1594 $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1595 break;
1596 }
1597 } finally {
1598 $worksheet->setStringEscaping($string_escaping_org_value);
1599 }
1600 }
1601 }
1602 $i++;
1603 }
1604
1605 return $startrow + $i + 1;
1606 }
1607
1612 {
1613 // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1614 //$this->setClozeText( $migrator->migrateToLmContent($this->getClozeText()) );
1615 $this->cloze_text = $migrator->migrateToLmContent($this->getClozeText());
1616 // DO NOT USE SETTER FOR CLOZE TEXT -> SETTER DOES RECREATE GAP OBJECTS without having gap type info ^^
1617 }
1618
1622 public function toJSON()
1623 {
1624 include_once("./Services/RTE/classes/class.ilRTE.php");
1625 $result = array();
1626 $result['id'] = (int) $this->getId();
1627 $result['type'] = (string) $this->getQuestionType();
1628 $result['title'] = (string) $this->getTitle();
1629 $result['question'] = $this->formatSAQuestion($this->getQuestion());
1630 $result['clozetext'] = $this->formatSAQuestion($this->getClozeText());
1631 $result['nr_of_tries'] = (int) $this->getNrOfTries();
1632 $result['shuffle'] = (bool) $this->getShuffle();
1633 $result['feedback'] = array(
1634 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)),
1635 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true))
1636 );
1637
1638 $gaps = array();
1639 foreach ($this->getGaps() as $key => $gap) {
1640 $items = array();
1641 foreach ($gap->getItems($this->getShuffler()) as $item) {
1642 $jitem = array();
1643 $jitem['points'] = $item->getPoints();
1644 $jitem['value'] = $this->formatSAQuestion($item->getAnswertext());
1645 $jitem['order'] = $item->getOrder();
1646 if ($gap->getType() == CLOZE_NUMERIC) {
1647 $jitem['lowerbound'] = $item->getLowerBound();
1648 $jitem['upperbound'] = $item->getUpperBound();
1649 } else {
1650 $jitem['value'] = trim($jitem['value']);
1651 }
1652 array_push($items, $jitem);
1653 }
1654
1655 if ($gap->getGapSize() && ($gap->getType() == CLOZE_TEXT || $gap->getType() == CLOZE_NUMERIC)) {
1656 $jgap['size'] = $gap->getGapSize();
1657 }
1658
1659 $jgap['shuffle'] = $gap->getShuffle();
1660 $jgap['type'] = $gap->getType();
1661 $jgap['item'] = $items;
1662
1663 array_push($gaps, $jgap);
1664 }
1665 $result['gaps'] = $gaps;
1666 $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
1667 $result['mobs'] = $mobs;
1668 return json_encode($result);
1669 }
1670
1679 public function getOperators($expression)
1680 {
1681 require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1683 }
1684
1689 public function getExpressionTypes()
1690 {
1691 return array(
1697 );
1698 }
1699
1708 public function getUserQuestionResult($active_id, $pass)
1709 {
1711 global $DIC;
1712 $ilDB = $DIC['ilDB'];
1713 $result = new ilUserQuestionResult($this, $active_id, $pass);
1714
1715 $maxStep = $this->lookupMaxStep($active_id, $pass);
1716
1717 if ($maxStep !== null) {
1718 $data = $ilDB->queryF(
1719 "
1720 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1721 FROM tst_solutions sol
1722 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1723 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1724 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1725 ",
1726 array("integer", "integer", "integer","integer"),
1727 array($active_id, $pass, $this->getId(), $maxStep)
1728 );
1729 } else {
1730 $data = $ilDB->queryF(
1731 "
1732 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1733 FROM tst_solutions sol
1734 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1735 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1736 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1737 ",
1738 array("integer", "integer", "integer"),
1739 array($active_id, $pass, $this->getId())
1740 );
1741 }
1742
1743 while ($row = $ilDB->fetchAssoc($data)) {
1744 if ($row["cloze_type"] == 1) {
1745 $row["value2"]++;
1746 }
1747 $result->addKeyValue($row["val"], $row["value2"]);
1748 }
1749
1750 $points = $this->calculateReachedPoints($active_id, $pass);
1751 $max_points = $this->getMaximumPoints();
1752
1753 $result->setReachedPercentage(($points / $max_points) * 100);
1754
1755 return $result;
1756 }
1757
1766 public function getAvailableAnswerOptions($index = null)
1767 {
1768 if ($index !== null) {
1769 return $this->getGap($index);
1770 } else {
1771 return $this->getGaps();
1772 }
1773 }
1774
1775 public function calculateCombinationResult($user_result)
1776 {
1777 $points = 0;
1778
1779 $assClozeGapCombinationObj = new assClozeGapCombination();
1780
1781 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1782 $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->getId());
1783 $gap_answers = array();
1784 $gap_used_in_combination = array();
1785 foreach ($user_result as $user_result_build_list) {
1786 if (is_array($user_result_build_list)) {
1787 $gap_answers[$user_result_build_list['gap_id']] = $user_result_build_list['value'];
1788 }
1789 }
1790
1791 foreach ($combinations_for_question as $combination) {
1792 foreach ($combination as $row_key => $row_answers) {
1793 $combination_fulfilled = true;
1794 $points_for_combination = $row_answers['points'];
1795 foreach ($row_answers as $gap_key => $combination_gap_answer) {
1796 if ($gap_key !== 'points') {
1797 $gap_used_in_combination[$gap_key] = $gap_key;
1798 }
1799 if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1800 switch ($combination_gap_answer['type']) {
1801 case CLOZE_TEXT:
1802 $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer['answer'], 1);
1803 if ($is_text_gap_correct != 1) {
1804 $combination_fulfilled = false;
1805 }
1806 break;
1807 case CLOZE_SELECT:
1808 $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1809 $answertext = $answer->getAnswertext();
1810 if ($answertext != $combination_gap_answer['answer']) {
1811 $combination_fulfilled = false;
1812 }
1813 break;
1814 case CLOZE_NUMERIC:
1815 $answer = $this->gaps[$gap_key]->getItem(0);
1816 if ($combination_gap_answer['answer'] != 'out_of_bound') {
1817 $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1818 if ($is_numeric_gap_correct != 1) {
1819 $combination_fulfilled = false;
1820 }
1821 } else {
1822 $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1823 if ($wrong_is_the_new_right == 1) {
1824 $combination_fulfilled = false;
1825 }
1826 }
1827 break;
1828 }
1829 } else {
1830 if ($gap_key !== 'points') {
1831 $combination_fulfilled = false;
1832 }
1833 }
1834 }
1835 if ($combination_fulfilled) {
1836 $points += $points_for_combination;
1837 }
1838 }
1839 }
1840 }
1841 return array($points, $gap_used_in_combination);
1842 }
1848 protected function calculateReachedPointsForSolution($user_result, &$detailed = null)
1849 {
1850 if ($detailed === null) {
1851 $detailed = array();
1852 }
1853
1854 $assClozeGapCombinationObj = new assClozeGapCombination();
1855 $combinations[1] = array();
1856 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1857 $combinations = $this->calculateCombinationResult($user_result);
1858 $points = $combinations[0];
1859 }
1860 $counter = 0;
1861 $solution_values_text = array(); // for identical scoring checks
1862 $solution_values_select = array(); // for identical scoring checks
1863 $solution_values_numeric = array(); // for identical scoring checks
1864 foreach ($user_result as $gap_id => $value) {
1865 if (is_string($value)) {
1866 $value = array("value" => $value);
1867 }
1868
1869 if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1870 switch ($this->gaps[$gap_id]->getType()) {
1871 case CLOZE_TEXT:
1872 $gappoints = 0;
1873 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1874 $answer = $this->gaps[$gap_id]->getItem($order);
1875 $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints());
1876 if ($gotpoints > $gappoints) {
1877 $gappoints = $gotpoints;
1878 }
1879 }
1880 if (!$this->getIdenticalScoring()) {
1881 // check if the same solution text was already entered
1882 if ((in_array($value["value"], $solution_values_text)) && ($gappoints > 0)) {
1883 $gappoints = 0;
1884 }
1885 }
1886 $points += $gappoints;
1887 $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1888 array_push($solution_values_text, $value["value"]);
1889 break;
1890 case CLOZE_NUMERIC:
1891 $gappoints = 0;
1892 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1893 $answer = $this->gaps[$gap_id]->getItem($order);
1894 $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value["value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1895 if ($gotpoints > $gappoints) {
1896 $gappoints = $gotpoints;
1897 }
1898 }
1899 if (!$this->getIdenticalScoring()) {
1900 // check if the same solution value was already entered
1901 include_once "./Services/Math/classes/class.EvalMath.php";
1902 $eval = new EvalMath();
1903 $eval->suppress_errors = true;
1904 $found_value = false;
1905 foreach ($solution_values_numeric as $solval) {
1906 if ($eval->e($solval) == $eval->e($value["value"])) {
1907 $found_value = true;
1908 }
1909 }
1910 if ($found_value && ($gappoints > 0)) {
1911 $gappoints = 0;
1912 }
1913 }
1914 $points += $gappoints;
1915 $detailed[$gap_id] = array("points" => $gappoints, "best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ? true : false, "positive" => ($gappoints > 0) ? true : false);
1916 array_push($solution_values_numeric, $value["value"]);
1917 break;
1918 case CLOZE_SELECT:
1919 if ($value["value"] >= 0) {
1920 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1921 $answer = $this->gaps[$gap_id]->getItem($order);
1922 if ($value["value"] == $answer->getOrder()) {
1923 $answerpoints = $answer->getPoints();
1924 if (!$this->getIdenticalScoring()) {
1925 // check if the same solution value was already entered
1926 if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0)) {
1927 $answerpoints = 0;
1928 }
1929 }
1930 $points += $answerpoints;
1931 $detailed[$gap_id] = array("points" => $answerpoints, "best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ? true : false, "positive" => ($answerpoints > 0) ? true : false);
1932 array_push($solution_values_select, $answer->getAnswertext());
1933 }
1934 }
1935 }
1936 break;
1937 }
1938 }
1939 }
1940
1941 return $points;
1942 }
1943
1945 {
1946 $userSolution = array();
1947
1948 foreach ($previewSession->getParticipantsSolution() as $key => $val) {
1949 // fau: fixEmptyGapPreview - add the gap_id as array index
1950 $userSolution[$key] = array('gap_id' => $key, 'value' => $val);
1951 // fau.
1952 }
1953
1954 $reachedPoints = $this->calculateReachedPointsForSolution($userSolution);
1955 $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $reachedPoints);
1956
1957 return $this->ensureNonNegativePoints($reachedPoints);
1958 }
1959
1960 public function fetchAnswerValueForGap($userSolution, $gapIndex)
1961 {
1962 $answerValue = '';
1963
1964 foreach ($userSolution as $value1 => $value2) {
1965 if ($value1 == $gapIndex) {
1966 $answerValue = $value2;
1967 break;
1968 }
1969 }
1970
1971 return $answerValue;
1972 }
1973
1974 public function isAddableAnswerOptionValue($qIndex, $answerOptionValue)
1975 {
1976 $gap = $this->getGap($qIndex);
1977
1978 if ($gap->getType() != CLOZE_TEXT) {
1979 return false;
1980 }
1981
1982 foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) {
1983 if ($item->getAnswertext() == $answerOptionValue) {
1984 return false;
1985 }
1986 }
1987
1988 return true;
1989 }
1990
1991 public function addAnswerOptionValue($qIndex, $answerOptionValue, $points)
1992 {
1993 $gap = $this->getGap($qIndex); /* @var assClozeGap $gap */
1994
1995 $item = new assAnswerCloze($answerOptionValue, $points);
1996 $item->setOrder($gap->getItemCount());
1997
1998 $gap->addItem($item);
1999 }
2000
2001 public function savePartial()
2002 {
2003 return true;
2004 }
2005}
$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.
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
$key
Definition: croninfo.php:18
$i
Definition: disco.tpl.php:19
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:60
$row
$query
global $DIC
Definition: saml.php:7
global $ilDB
$mobs
$ilUser
Definition: imgupload.php:18
$data
Definition: bench.php:6
$text
Definition: errorreport.php:18