19declare(strict_types=1);
92 $this->randomGroup =
$DIC->refinery()->random();
121 if ($text ===
null) {
125 $text = str_replace(
'$',
'GAPMASKEDDOLLAR', $text);
126 $text = preg_replace(
"/\[gap[^\]]*?\]/",
"[gap]", $text);
127 $text = preg_replace(
"/<gap([^>]*?)>/",
"[gap]", $text);
128 $text = str_replace(
"</gap>",
"[/gap]", $text);
129 $text = str_replace(
'GAPMASKEDDOLLAR',
'$', $text);
138 $output = preg_replace(
139 '/\[gap\].*?\[\/gap\]/',
140 str_replace(
'$',
'GAPMASKEDDOLLAR', $content),
144 return str_replace(
'GAPMASKEDDOLLAR',
'$', $output);
149 $result = $this->db->queryF(
150 "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",
154 if ($result->numRows() == 1) {
155 $data = $this->db->fetchAssoc($result);
156 $this->setId($question_id);
157 $this->setNrOfTries(
$data[
'nr_of_tries']);
158 $this->setObjId(
$data[
"obj_fi"]);
159 $this->setTitle((
string)
$data[
"title"]);
160 $this->setComment((
string)
$data[
"description"]);
161 $this->setOriginalId(
$data[
"original_id"]);
162 $this->setAuthor(
$data[
"author"]);
163 $this->setPoints(
$data[
"points"]);
164 $this->setOwner(
$data[
"owner"]);
165 $this->setQuestion($this->cleanQuestiontext(
$data[
"question_text"]));
166 $this->setClozeText(
$data[
'cloze_text'] ??
'');
167 $this->setFixedTextLength(
$data[
"fixed_textlen"]);
168 $this->setIdenticalScoring((
$data[
'tstamp'] === 0) ?
true : (
bool)
$data[
'identical_scoring']);
179 $this->setTextgapRating(
$data[
"textgap_rating"]);
182 $this->setAdditionalContentEditingMode(
$data[
'add_cont_edit_mode']);
186 $result = $this->db->queryF(
187 "SELECT * FROM qpl_a_cloze WHERE question_fi = %s ORDER BY gap_id, aorder ASC",
191 if ($result->numRows() > 0) {
193 while (
$data = $this->db->fetchAssoc($result)) {
194 switch (
$data[
"cloze_type"]) {
196 if (!array_key_exists(
$data[
"gap_id"], $this->gaps)) {
204 $this->gaps[
$data[
"gap_id"]]->setGapSize((
int)
$data[
'gap_size']);
206 $this->gaps[
$data[
"gap_id"]]->addItem($answer);
209 if (!array_key_exists(
$data[
"gap_id"], $this->gaps)) {
211 $this->gaps[
$data[
"gap_id"]]->setShuffle(
$data[
"shuffle"]);
218 $this->gaps[
$data[
"gap_id"]]->addItem($answer);
221 if (!array_key_exists(
$data[
"gap_id"], $this->gaps)) {
229 $this->gaps[
$data[
"gap_id"]]->setGapSize((
int)
$data[
'gap_size']);
230 $answer->setLowerBound(
$data[
"lowerlimit"]);
231 $answer->setUpperBound(
$data[
"upperlimit"]);
232 $this->gaps[
$data[
"gap_id"]]->addItem($answer);
239 if (count($check_for_gap_combinations) != 0) {
240 $this->setGapCombinationsExists(
true);
241 $this->setGapCombinations($check_for_gap_combinations);
243 parent::loadFromDb($question_id);
246 public function saveToDb(?
int $original_id =
null): void
248 $this->saveQuestionDataToDb($original_id);
249 $this->saveAdditionalQuestionDataToDb();
250 $this->saveAnswerSpecificDataToDb();
257 $this->db->manipulateF(
258 "DELETE FROM qpl_a_cloze WHERE question_fi = %s",
263 foreach ($this->gaps as $key => $gap) {
264 $this->saveClozeGapItemsToDb($gap, $key);
270 $this->db->manipulateF(
271 "DELETE FROM " . $this->getAdditionalTableName() .
" WHERE question_fi = %s",
276 $this->db->insert($this->getAdditionalTableName(), [
277 'question_fi' => [
'integer', $this->
getId()],
278 'textgap_rating' => [
'text', $this->getTextgapRating()],
279 'identical_scoring' => [
'text', $this->getIdenticalScoring()],
280 'fixed_textlen' => [
'integer', $this->getFixedTextLength() ? $this->getFixedTextLength() :
null],
282 'feedback_mode' => [
'text', $this->getFeedbackMode()]
289 foreach ($gap->getItems($this->getShuffler()) as $item) {
290 $next_id = $this->db->nextId(
'qpl_a_cloze');
293 $this->saveClozeTextGapRecordToDb($next_id, $key, $item, $gap);
296 $this->saveClozeSelectGapRecordToDb($next_id, $key, $item, $gap);
299 $this->saveClozeNumericGapRecordToDb($next_id, $key, $item, $gap);
311 $this->db->manipulateF(
312 '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)',
327 strlen($item->getAnswertext()) ? $item->getAnswertext() :
'',
331 (
int) $gap->getGapSize()
342 $this->db->manipulateF(
343 '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)',
358 strlen($item->getAnswertext()) ? $item->getAnswertext() :
'',
362 ($gap->getShuffle()) ?
'1' :
'0'
374 $eval->suppress_errors =
true;
375 $this->db->manipulateF(
376 '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)',
421 $this->cloze_text = $this->cleanQuestiontext($cloze_text);
422 $this->createGapsFromQuestiontext();
427 $this->cloze_text = $cloze_text;
439 return $this->cloze_text;
453 preg_match_all(
'/\[gap\].*?\[\/gap\]/', $this->getClozeText(), $gaps);
454 $string_with_replaced_gaps = str_replace($gaps[0],
'######GAP######', $this->getClozeText());
455 $cleaned_text = $this->getHtmlQuestionContentPurifier()->purify(
456 $string_with_replaced_gaps
458 $cleaned_text_with_gaps = preg_replace_callback(
'/######GAP######/',
function ($match) use (&$gaps) {
459 return array_shift($gaps[0]);
462 if ($this->isAdditionalContentEditingModePageObject()
463 || !(
new ilSetting(
'advanced_editing'))->
get(
'advanced_editing_javascript_editor') ===
'tinymce') {
464 $cleaned_text_with_gaps = nl2br($cleaned_text_with_gaps);
479 return $this->start_tag;
491 $this->start_tag = $start_tag;
503 return $this->end_tag;
515 $this->end_tag = $end_tag;
523 return $this->feedbackMode;
531 $this->feedbackMode = $feedbackMode;
542 $search_pattern =
"|\[gap\](.*?)\[/gap\]|i";
543 preg_match_all($search_pattern, $this->getClozeText(), $found);
545 if (count($found[0])) {
546 foreach ($found[1] as $gap_index => $answers) {
549 $textparams = preg_split(
"/(?<!\\\\),/", $answers);
550 foreach ($textparams as $key => $value) {
554 $this->gaps[$gap_index] = $gap;
566 if (array_key_exists($gap_index, $this->gaps)) {
567 $this->gaps[$gap_index]->setType($gap_type);
582 if (array_key_exists($gap_index, $this->gaps)) {
583 $this->gaps[$gap_index]->setShuffle($shuffle);
595 foreach ($this->gaps as $gap_index => $gap) {
596 $this->gaps[$gap_index]->clearItems();
609 if (is_array($this->gaps)) {
610 return count($this->gaps);
628 if (array_key_exists($gap_index, $this->gaps)) {
631 $answer = str_replace(
",",
".", $answer);
633 $this->gaps[$gap_index]->addItem(
new assAnswerCloze(trim($answer), 0, $order));
639 if (array_key_exists($gap_index, $this->gaps)) {
640 return $this->gaps[$gap_index];
647 if (array_key_exists($gap_index, $this->gaps)) {
648 $this->gaps[$gap_index]->setGapSize((
int) $size);
664 if (array_key_exists($gap_index, $this->gaps)) {
665 $this->gaps[$gap_index]->setItemPoints($order, $points);
679 if (array_key_exists($gap_index, $this->gaps)) {
683 $this->gaps[$gap_index]->getItemCount()
685 $this->gaps[$gap_index]->addItem($answer);
699 $this->gaps[$index] = $gap;
714 if (array_key_exists($gap_index, $this->gaps)) {
715 $this->gaps[$gap_index]->setItemLowerBound($order, $bound);
731 if (array_key_exists($gap_index, $this->gaps)) {
732 $this->gaps[$gap_index]->setItemUpperBound($order, $bound);
746 $gaps_used_in_combination = [];
747 if ($this->gap_combinations_exist) {
748 $points = $assClozeGapCombinationObj->getMaxPointsForCombination($this->
getId());
749 $gaps_used_in_combination = $assClozeGapCombinationObj->getGapsWhichAreUsedInCombination($this->
getId());
751 foreach ($this->gaps as $gap_index => $gap) {
752 if (!array_key_exists($gap_index, $gaps_used_in_combination)) {
755 foreach ($gap->
getItems($this->getShuffler()) as $item) {
756 if ($item->getPoints() > $gap_max_points) {
757 $gap_max_points = $item->getPoints();
760 $points += $gap_max_points;
763 foreach ($gap->
getItems($this->getShuffler()) as $item) {
764 if ($item->getPoints() > $srpoints) {
765 $srpoints = $item->getPoints();
768 $points += $srpoints;
771 foreach ($gap->
getItems($this->getShuffler()) as $item) {
772 if ($item->getPoints() > $numpoints) {
773 $numpoints = $item->getPoints();
776 $points += $numpoints;
787 if ($this->gap_combinations_exist) {
789 $gap_combination->clearGapCombinationsFromDb($target->
getId());
790 $gap_combination->importGapCombinationToDb(
792 $this->gap_combinations,
805 $output = $this->getClozeText();
806 foreach ($this->getGaps() as $gap_index => $gap) {
809 array_push($answers, str_replace([
',',
'['], [
"\\,",
'[ '], $item->getAnswerText()));
815 $output = str_replace(
"_gap]",
"gap]", $output);
816 $this->cloze_text = $output;
830 if (array_key_exists($gap_index, $this->gaps)) {
831 if ($this->gaps[$gap_index]->getItemCount() == 1) {
833 $this->deleteGap($gap_index);
836 $this->gaps[$gap_index]->deleteItem($answer_index);
837 $this->updateClozeTextFromGaps();
852 if (array_key_exists($gap_index, $this->gaps)) {
853 $output = $this->getClozeText();
854 foreach ($this->getGaps() as $replace_gap_index => $gap) {
857 array_push($answers, str_replace(
",",
"\\,", $item->getAnswerText()));
859 if ($replace_gap_index == $gap_index) {
861 $output = $this->replaceFirstGap($output,
'');
865 $output = $this->replaceFirstGap($output,
"[_gap]" . join(
",", $answers) .
"[/_gap]");
869 $output = str_replace(
"_gap]",
"gap]", $output);
870 $this->cloze_text = $output;
871 unset($this->gaps[$gap_index]);
872 $this->gaps = array_values($this->gaps);
890 $gaprating = $this->getTextgapRating();
892 switch ($gaprating) {
895 $result = $max_points;
899 if (strcmp($a_original, $a_entered) == 0) {
900 $result = $max_points;
904 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 1);
907 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 2);
910 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 3);
913 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 4);
916 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 5);
921 if (isset($transformation) && $transformation->transform($a_entered) >= 0) {
922 $result = $max_points;
937 public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound): float
940 $eval->suppress_errors =
true;
943 if ($eval->e($a_entered) ===
false) {
945 } elseif (($eval->e($lowerBound) !==
false) && ($eval->e($upperBound) !==
false)) {
946 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
947 $result = $max_points;
949 } elseif ($eval->e($lowerBound) !==
false) {
950 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
951 $result = $max_points;
953 } elseif ($eval->e($upperBound) !==
false) {
954 if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
955 $result = $max_points;
957 } elseif ($eval->e($a_entered) == $eval->e($a_original)) {
958 $result = $max_points;
965 return preg_match(
"/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
971 bool $authorized_solution =
true
973 $user_result = $this->fetchUserResult($active_id, $pass, $authorized_solution);
974 return $this->calculateReachedPointsForSolution($user_result);
980 bool $authorized_solution =
true
982 $user_result = $this->fetchUserResult($active_id, $pass, $authorized_solution);
984 $this->calculateReachedPointsForSolution($user_result, $detailed);
992 if (is_null($pass)) {
993 $pass = $this->getSolutionMaxPass($active_id);
996 $result = $this->getCurrentSolutionResultSet($active_id, $pass,
true);
998 while (
$data = $this->db->fetchAssoc($result)) {
999 if (
$data[
'value2'] ===
'') {
1002 $user_result[
$data[
'value1']] = [
1003 'gap_id' =>
$data[
'value1'],
1004 'value' =>
$data[
'value2']
1008 ksort($user_result);
1009 return $user_result;
1014 if (is_numeric($submittedValue)) {
1018 if (preg_match(
'/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1027 $solution_submit = [];
1028 $post_wrapper = $this->dic->http()->wrapper()->post();
1029 foreach ($this->getGaps() as $index => $gap) {
1030 if (!$post_wrapper->has(
"gap_$index")) {
1033 $value = trim($post_wrapper->retrieve(
1035 $this->dic->refinery()->kindlyTo()->string()
1037 if ($value ===
'') {
1046 $value = str_replace(
',',
'.', $value);
1047 if (!is_numeric($value)) {
1052 $solution_submit[$index] = $value;
1055 return $solution_submit;
1060 return $this->fetchSolutionSubmit();
1066 bool $authorized =
true
1068 if (is_null($pass)) {
1072 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
1073 function () use ($active_id, $pass, $authorized) {
1074 $this->removeCurrentSolution($active_id, $pass, $authorized);
1076 foreach ($this->fetchSolutionSubmit() as $key => $value) {
1077 if ($value ===
null || $value ===
'') {
1080 $gap = $this->getGap($key);
1085 $this->saveCurrentSolution($active_id, $pass, $key, $value, $authorized);
1101 return "assClozeTest";
1113 return $this->textgap_rating;
1125 switch ($a_textgap_rating) {
1133 $this->textgap_rating = $a_textgap_rating;
1150 return $this->identical_scoring;
1162 $this->identical_scoring = $identical_scoring;
1173 return "qpl_qst_cloze";
1178 return [
"qpl_a_cloze",
'qpl_a_cloze_combi_res'];
1189 $this->fixed_text_length = $fixed_text_length;
1200 return $this->fixed_text_length;
1214 $gap_max_points = 0;
1215 if (array_key_exists($gap_index, $this->gaps)) {
1216 $gap = &$this->gaps[$gap_index];
1217 foreach ($gap->
getItems($this->getShuffler()) as $answer) {
1218 if ($answer->getPoints() > $gap_max_points) {
1219 $gap_max_points = $answer->getPoints();
1222 $points += $gap_max_points;
1233 return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1237 return $this->gap_combinations_exist;
1242 return $this->gap_combinations;
1247 $this->gap_combinations_exist = $value;
1252 $this->gap_combinations = $value;
1272 'id' => $this->
getId(),
1273 'type' => (string) $this->getQuestionType(),
1274 'title' => $this->getTitleForHTMLOutput(),
1275 'question' => $this->formatSAQuestion($this->getQuestion()),
1276 'clozetext' => $this->formatSAQuestion($this->getClozeText()),
1277 'nr_of_tries' => $this->getNrOfTries(),
1278 'shuffle' => $this->getShuffle(),
1280 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
1281 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
1286 foreach ($this->getGaps() as $key => $gap) {
1288 foreach ($gap->
getItems($this->getShuffler()) as $item) {
1290 $jitem[
'points'] = $item->getPoints();
1291 $jitem[
'value'] = $this->formatSAQuestion($item->getAnswertext());
1292 $jitem[
'order'] = $item->getOrder();
1294 $jitem[
'lowerbound'] = $item->getLowerBound();
1295 $jitem[
'upperbound'] = $item->getUpperBound();
1297 $jitem[
'value'] = trim($jitem[
'value']);
1299 array_push($items, $jitem);
1307 $jgap[
'type'] = $gap->
getType();
1308 $jgap[
'item'] = $items;
1310 array_push($gaps, $jgap);
1312 $result[
'gaps'] = $gaps;
1314 $result[
'mobs'] = $mobs;
1315 return json_encode($result);
1340 $maxStep = $this->lookupMaxStep($active_id, $pass);
1342 $data = $this->db->queryF(
1344 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1345 FROM tst_solutions sol
1346 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1347 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1348 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1350 [
"integer",
"integer",
"integer",
"integer"],
1351 [$active_id, $pass, $this->
getId(), $maxStep]
1354 $data = $this->db->queryF(
1356 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1357 FROM tst_solutions sol
1358 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1359 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1360 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1362 [
"integer",
"integer",
"integer"],
1363 [$active_id, $pass, $this->
getId()]
1367 while ($row = $this->db->fetchAssoc(
$data)) {
1368 if ($row[
"cloze_type"] == 1) {
1371 $result->addKeyValue($row[
"val"], $row[
"value2"]);
1374 $points = $this->calculateReachedPoints($active_id, $pass);
1375 $max_points = $this->getMaximumPoints();
1377 $result->setReachedPercentage(($points / $max_points) * 100);
1392 if ($index !==
null) {
1393 return $this->getGap($index);
1395 return $this->getGaps();
1404 $gap_used_in_combination = [];
1405 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1406 $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->
getId());
1409 foreach ($user_result as $user_result_build_list) {
1410 if (is_array($user_result_build_list)) {
1411 $gap_answers[$user_result_build_list[
'gap_id']] = $user_result_build_list[
'value'];
1415 foreach ($combinations_for_question as $combination) {
1416 foreach ($combination as $row_key => $row_answers) {
1417 $combination_fulfilled =
true;
1418 $points_for_combination = $row_answers[
'points'];
1419 foreach ($row_answers as $gap_key => $combination_gap_answer) {
1420 if ($gap_key !==
'points') {
1421 $gap_used_in_combination[$gap_key] = $gap_key;
1423 if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1424 switch ($combination_gap_answer[
'type']) {
1426 $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer[
'answer'], 1);
1427 if ($is_text_gap_correct != 1) {
1428 $combination_fulfilled =
false;
1432 $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1433 $answertext = $answer?->getAnswertext();
1434 if ($answertext != $combination_gap_answer[
'answer']) {
1435 $combination_fulfilled =
false;
1439 $answer = $this->gaps[$gap_key]->getItem(0);
1440 if ($combination_gap_answer[
'answer'] !=
'out_of_bound') {
1441 $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1442 if ($is_numeric_gap_correct != 1) {
1443 $combination_fulfilled =
false;
1446 $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1447 if ($wrong_is_the_new_right == 1) {
1448 $combination_fulfilled =
false;
1454 if ($gap_key !==
'points') {
1455 $combination_fulfilled =
false;
1459 if ($combination_fulfilled) {
1460 $points += $points_for_combination;
1465 return [$points, $gap_used_in_combination];
1476 $combinations[1] = [];
1477 if ($this->gap_combinations_exist) {
1478 $combinations = $this->calculateCombinationResult($user_result);
1479 $points = $combinations[0];
1482 $solution_values_text = [];
1483 $solution_values_select = [];
1484 $solution_values_numeric = [];
1485 foreach ($user_result as $gap_id => $value) {
1486 if (is_string($value)) {
1487 $value = [
"value" => $value];
1490 if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1491 switch ($this->gaps[$gap_id]->getType()) {
1494 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1495 $answer = $this->gaps[$gap_id]->getItem($order);
1496 $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value[
"value"], $answer->getPoints());
1497 if ($gotpoints > $gappoints) {
1498 $gappoints = $gotpoints;
1501 if (!$this->getIdenticalScoring()) {
1503 if ((in_array($value[
"value"], $solution_values_text)) && ($gappoints > 0.0)) {
1507 $points += $gappoints;
1508 $detailed[$gap_id] = [
"points" => $gappoints,
"best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ?
true :
false,
"positive" => ($gappoints > 0.0) ?
true :
false];
1509 array_push($solution_values_text, $value[
"value"]);
1513 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1514 $answer = $this->gaps[$gap_id]->getItem($order);
1515 $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value[
"value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1516 if ($gotpoints > $gappoints) {
1517 $gappoints = $gotpoints;
1520 if (!$this->getIdenticalScoring()) {
1523 $eval->suppress_errors =
true;
1524 $found_value =
false;
1525 foreach ($solution_values_numeric as $solval) {
1526 if ($eval->e($solval) == $eval->e($value[
"value"])) {
1527 $found_value =
true;
1530 if ($found_value && ($gappoints > 0.0)) {
1534 $points += $gappoints;
1535 $detailed[$gap_id] = [
"points" => $gappoints,
"best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ?
true :
false,
"positive" => ($gappoints > 0.0) ?
true :
false];
1536 array_push($solution_values_numeric, $value[
"value"]);
1539 if ($value[
"value"] >= 0.0) {
1540 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1541 $answer = $this->gaps[$gap_id]->getItem($order);
1542 if ($value[
"value"] == $answer->getOrder()) {
1543 $answerpoints = $answer->getPoints();
1544 if (!$this->getIdenticalScoring()) {
1546 if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0.0)) {
1547 $answerpoints = 0.0;
1550 $points += $answerpoints;
1551 $detailed[$gap_id] = [
"points" => $answerpoints,
"best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ?
true :
false,
"positive" => ($answerpoints > 0.0) ?
true :
false];
1552 array_push($solution_values_select, $answer->getAnswertext());
1568 if (!is_array($participant_session)) {
1572 $user_solution = [];
1574 foreach ($participant_session as $key => $val) {
1575 $user_solution[$key] = [
'gap_id' => $key,
'value' => $val];
1578 $reached_points = $this->calculateReachedPointsForSolution($user_solution);
1580 return $this->ensureNonNegativePoints($reached_points);
1587 foreach ($userSolution as $value1 => $value2) {
1588 if ($value1 == $gapIndex) {
1589 $answerValue = $value2;
1594 return $answerValue;
1599 $gap = $this->getGap($qIndex);
1605 foreach ($gap->
getItems($this->randomGroup->dontShuffle()) as $item) {
1606 if ($item->getAnswertext() === $answerOptionValue) {
1616 $gap = $this->getGap($qIndex);
1627 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
1628 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
1629 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
1630 AdditionalInformationGenerator::KEY_QUESTION_CLOZE_CLOZETEXT => $this->formatSAQuestion($this->getClozeText()),
1631 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
1633 AdditionalInformationGenerator::KEY_FEEDBACK => [
1634 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
1635 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
1640 foreach ($this->getGaps() as $gap_index => $gap) {
1642 foreach ($gap->
getItems($this->getShuffler()) as $item) {
1644 AdditionalInformationGenerator::KEY_QUESTION_REACHABLE_POINTS => $item->getPoints(),
1645 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION => $this->formatSAQuestion($item->getAnswertext()),
1646 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => $item->getOrder()
1649 $item_array[AdditionalInformationGenerator::KEY_QUESTION_LOWER_LIMIT] = $item->getLowerBound();
1650 $item_array[AdditionalInformationGenerator::KEY_QUESTION_UPPER_LIMIT] = $item->getUpperBound();
1652 array_push($items, $item_array);
1655 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_TEXTSIZE] = $gap->
getGapSize();
1656 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS] = $additional_info->
getTrueFalseTagForBool(
1659 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_CLOZE_GAP_TYPE] = $gap->
getType();
1660 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTIONS] = $items;
1662 $gaps[$gap_index + 1] = $gap_array;
1664 $result[AdditionalInformationGenerator::KEY_QUESTION_CLOZE_GAPS] = $gaps;
1670 array $solution_values
1672 $parsed_solution = [];
1673 foreach ($this->getGaps() as $gap_index => $gap) {
1674 foreach ($solution_values as $solutionvalue) {
1675 if ($gap_index !== (
int) $solutionvalue[
'value1']) {
1680 $parsed_solution[$gap_index + 1] = $gap->
getItem($solutionvalue[
'value2'])->getAnswertext();
1684 $parsed_solution[$gap_index + 1] = $solutionvalue[
'value2'];
1687 return $parsed_solution;
1692 $parsed_solution = [];
1693 foreach ($this->getGaps() as $gap_index => $gap) {
1694 foreach ($solution_values as $solutionvalue) {
1695 if ($gap_index !== (
int) $solutionvalue[
'value1']) {
1700 $parsed_solution[] = $this->
lng->txt(
'gap') .
' ' . $gap_index + 1 .
': '
1701 . $gap->
getItem($solutionvalue[
'value2'])->getAnswertext();
1705 $parsed_solution[] = $this->
lng->txt(
'gap') .
' ' . $gap_index + 1 .
': '
1706 . $solutionvalue[
'value2'];
1709 return $parsed_solution;
1715 foreach ($this->getGaps() as $gap_index => $gap) {
1716 $correct_answers = array_map(
1717 fn(
int $v):
string => $gap->
getItem($v)->getAnswertext(),
1720 $answers[] = $this->
lng->txt(
'gap') .
' ' . $gap_index + 1 .
': '
1721 . implode(
',', $correct_answers);
setOrder($order=0)
Sets the order.
getOrder()
Gets the sort/display order.
getPoints()
Gets the points.
getAnswertext()
Gets the answer text.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getLowerBound()
Returns the lower bound.
getUpperBound()
Returns the upper bound.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class for cloze question gaps.
const TEXTGAP_RATING_CASESENSITIVE
const TEXTGAP_RATING_LEVENSHTEIN1
const TEXTGAP_RATING_LEVENSHTEIN5
getBestSolutionIndexes()
Returns the indexes of the best solutions for the gap.
const TEXTGAP_RATING_CASEINSENSITIVE
const TEXTGAP_RATING_LEVENSHTEIN4
getItemCount()
Gets the item count.
getShuffle()
Gets the shuffle state of the items.
const TEXTGAP_RATING_LEVENSHTEIN2
const TEXTGAP_RATING_LEVENSHTEIN3
getItem($a_index)
Gets the item with a given index.
getItemsRaw()
Gets the items of a cloze gap.
addItem($a_item)
Adds a gap item.
getItems(Transformation $shuffler, ?int $gap_index=null)
clearGapAnswers()
Removes all answers from the gaps.
getEndTag()
Returns the end tag of a cloze gap.
getCorrectSolutionForTextOutput(int $active_id, int $pass)
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.
replaceFirstGap(string $gaptext, string $content)
setGapSize($gap_index, $size)
saveClozeSelectGapRecordToDb(int $next_id, int $key, assAnswerCloze $item, assClozeGap $gap)
getClozeText()
Returns the cloze text.
setIdenticalScoring(bool $identical_scoring)
Sets the identical scoring option for cloze questions.
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...
string $textgap_rating
The rating option for text gaps.
saveClozeGapItemsToDb(assClozeGap $gap, int $key)
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
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.
setFeedbackMode($feedbackMode)
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 ...
setFixedTextLength(?int $fixed_text_length)
Sets a fixed text length for all text fields in the cloze question.
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.
saveClozeTextGapRecordToDb(int $next_id, int $key, assAnswerCloze $item, assClozeGap $gap)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
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.
setGapCombinations($value)
addAnswerOptionValue(int $qIndex, string $answerOptionValue, float $points)
updateClozeTextFromGaps()
Updates the gap parameters in the cloze text from the form input.
getStartTag()
Returns the start tag of a cloze gap.
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
MUST convert the given solution values into an array or a string that can be stored in the log.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
getFixedTextLength()
Gets the fixed text length for all text fields in the cloze question.
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="")
ilAssQuestionFeedback $feedbackOBJ
addGapText($gap_index)
Adds a new answer text value to a text gap with a given index.
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
saveClozeNumericGapRecordToDb(int $next_id, int $key, assAnswerCloze $item, assClozeGap $gap)
getOperators(string $expression)
Get all available operations for a specific question.
getQuestionType()
Returns the question type of the question.
setClozeText(string $cloze_text='')
getTextgapRating()
Returns the rating option for text gaps.
deleteGap($gap_index)
Deletes a gap with a given index.
bool $gap_combinations_exist
setGapType($gap_index, $gap_type)
Set the type of a gap with a given index.
getGapCombinationsExists()
getExpressionTypes()
Get all available expression types for a specific question.
checkForValidFormula(string $value)
getGapCount()
Returns the number of gaps.
setGapCombinationsExists($value)
getUserResultDetails(int $active_id, ?int $pass=null, bool $authorized_solution=true)
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 a record to the question types additional data table.
bool $identical_scoring
Defines the scoring for "identical solutions".
deleteAnswerText($gap_index, $answer_index)
Deletes the answer text of a gap with a given index and an answer with a given order.
toJSON()
Returns a JSON representation of the question.
calculateReachedPointsForSolution(?array $user_result, array &$detailed=[])
isAddableAnswerOptionValue(int $qIndex, string $answerOptionValue)
saveToDb(?int $original_id=null)
calculateCombinationResult($user_result)
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
setStartTag($start_tag="[gap]")
Sets the start tag of a cloze gap.
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
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...
cleanQuestiontext($text)
Cleans cloze question text to remove attributes or tags from older ILIAS versions.
loadFromDb(int $question_id)
getIdenticalScoring()
Returns the identical scoring status of the question.
getClozeTextForHTMLOutput()
Returns the cloze text as HTML (with optional nl2br) Fix for Mantis 29987: We assume Tiny embeds any ...
fetchUserResult(int $active_id, ?int $pass)
cloneQuestionTypeSpecificProperties(\assQuestion $target)
setQuestion(string $question="")
const FB_MODE_GAP_QUESTION
constants for different feedback modes (per gap or per gap-answers/options)
static getDraftInstance()
static getInstance($identifier)
getParticipantsSolution()
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
static getOperatorsByExpression(string $expression)
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
static strToLower(string $a_string)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const PercentageResultExpression
const StringResultExpression
const NumericResultExpression
const EmptyAnswerExpression
const NumberOfResultExpression
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
migrateToLmContent($content)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
if(!file_exists('../ilias.ini.php'))