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 $item->getAnswertext(),
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 $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,
810 $output = $this->getClozeText();
811 foreach ($this->getGaps() as $gap_index => $gap) {
814 array_push($answers, str_replace([
',',
'['], [
"\\,",
'[ '], $item->getAnswerText()));
820 $output = str_replace(
"_gap]",
"gap]", $output);
821 $this->cloze_text = $output;
835 if (array_key_exists($gap_index, $this->gaps)) {
836 if ($this->gaps[$gap_index]->getItemCount() == 1) {
838 $this->deleteGap($gap_index);
841 $this->gaps[$gap_index]->deleteItem($answer_index);
842 $this->updateClozeTextFromGaps();
857 if (array_key_exists($gap_index, $this->gaps)) {
858 $output = $this->getClozeText();
859 foreach ($this->getGaps() as $replace_gap_index => $gap) {
862 array_push($answers, str_replace(
",",
"\\,", $item->getAnswerText()));
864 if ($replace_gap_index == $gap_index) {
866 $output = $this->replaceFirstGap($output,
'');
870 $output = $this->replaceFirstGap($output,
"[_gap]" . join(
",", $answers) .
"[/_gap]");
874 $output = str_replace(
"_gap]",
"gap]", $output);
875 $this->cloze_text = $output;
876 unset($this->gaps[$gap_index]);
877 $this->gaps = array_values($this->gaps);
895 $gaprating = $this->getTextgapRating();
897 switch ($gaprating) {
900 $result = $max_points;
904 if (strcmp($a_original, $a_entered) == 0) {
905 $result = $max_points;
909 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 1);
912 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 2);
915 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 3);
918 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 4);
921 $transformation =
$refinery->string()->levenshtein()->standard($a_original, 5);
926 if (isset($transformation) && $transformation->transform($a_entered) >= 0) {
927 $result = $max_points;
942 public function getNumericgapPoints($a_original, $a_entered, $max_points, $lowerBound, $upperBound): float
945 $eval->suppress_errors =
true;
948 if ($eval->e($a_entered) ===
false) {
950 } elseif (($eval->e($lowerBound) !==
false) && ($eval->e($upperBound) !==
false)) {
951 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
952 $result = $max_points;
954 } elseif ($eval->e($lowerBound) !==
false) {
955 if (($eval->e($a_entered) >= $eval->e($lowerBound)) && ($eval->e($a_entered) <= $eval->e($a_original))) {
956 $result = $max_points;
958 } elseif ($eval->e($upperBound) !==
false) {
959 if (($eval->e($a_entered) >= $eval->e($a_original)) && ($eval->e($a_entered) <= $eval->e($upperBound))) {
960 $result = $max_points;
962 } elseif ($eval->e($a_entered) == $eval->e($a_original)) {
963 $result = $max_points;
970 return preg_match(
"/^-?(\\d*)(,|\\.|\\/){0,1}(\\d*)$/", $value, $matches);
976 bool $authorized_solution =
true
978 $user_result = $this->fetchUserResult($active_id, $pass, $authorized_solution);
979 return $this->calculateReachedPointsForSolution($user_result);
985 bool $authorized_solution =
true
987 $user_result = $this->fetchUserResult($active_id, $pass, $authorized_solution);
989 $this->calculateReachedPointsForSolution($user_result, $detailed);
997 if (is_null($pass)) {
998 $pass = $this->getSolutionMaxPass($active_id);
1001 $result = $this->getCurrentSolutionResultSet($active_id, $pass,
true);
1003 while (
$data = $this->db->fetchAssoc($result)) {
1004 if (
$data[
'value2'] ===
'') {
1007 $user_result[
$data[
'value1']] = [
1008 'gap_id' =>
$data[
'value1'],
1009 'value' =>
$data[
'value2']
1013 ksort($user_result);
1014 return $user_result;
1019 if (is_numeric($submittedValue)) {
1023 if (preg_match(
'/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
1032 $solution_submit = [];
1033 $post_wrapper = $this->dic->http()->wrapper()->post();
1034 foreach ($this->getGaps() as $index => $gap) {
1035 if (!$post_wrapper->has(
"gap_$index")) {
1038 $value = trim($post_wrapper->retrieve(
1040 $this->dic->refinery()->kindlyTo()->string()
1042 if ($value ===
'') {
1051 $value = str_replace(
',',
'.', $value);
1052 if (!is_numeric($value)) {
1057 $solution_submit[$index] = $value;
1060 return $solution_submit;
1065 return $this->fetchSolutionSubmit();
1071 bool $authorized =
true
1073 if (is_null($pass)) {
1077 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
1078 function () use ($active_id, $pass, $authorized) {
1079 $this->removeCurrentSolution($active_id, $pass, $authorized);
1081 foreach ($this->fetchSolutionSubmit() as $key => $value) {
1082 if ($value ===
null || $value ===
'') {
1085 $gap = $this->getGap($key);
1090 $this->saveCurrentSolution($active_id, $pass, $key, $value, $authorized);
1106 return "assClozeTest";
1118 return $this->textgap_rating;
1130 switch ($a_textgap_rating) {
1138 $this->textgap_rating = $a_textgap_rating;
1155 return $this->identical_scoring;
1167 $this->identical_scoring = $identical_scoring;
1178 return "qpl_qst_cloze";
1183 return [
"qpl_a_cloze",
'qpl_a_cloze_combi_res'];
1194 $this->fixed_text_length = $fixed_text_length;
1205 return $this->fixed_text_length;
1219 $gap_max_points = 0;
1220 if (array_key_exists($gap_index, $this->gaps)) {
1221 $gap = &$this->gaps[$gap_index];
1222 foreach ($gap->
getItems($this->getShuffler()) as $answer) {
1223 if ($answer->getPoints() > $gap_max_points) {
1224 $gap_max_points = $answer->getPoints();
1227 $points += $gap_max_points;
1238 return parent::getRTETextWithMediaObjects() . $this->getClozeText();
1242 return $this->gap_combinations_exist;
1247 return $this->gap_combinations;
1252 $this->gap_combinations_exist = $value;
1257 $this->gap_combinations = $value;
1277 'id' => $this->
getId(),
1278 'type' => (string) $this->getQuestionType(),
1279 'title' => $this->getTitleForHTMLOutput(),
1280 'question' => $this->formatSAQuestion($this->getQuestion()),
1281 'clozetext' => $this->formatSAQuestion($this->getClozeText()),
1282 'nr_of_tries' => $this->getNrOfTries(),
1283 'shuffle' => $this->getShuffle(),
1285 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
1286 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
1291 foreach ($this->getGaps() as $key => $gap) {
1293 foreach ($gap->
getItems($this->getShuffler()) as $item) {
1295 $jitem[
'points'] = $item->getPoints();
1296 $jitem[
'value'] = $this->formatSAQuestion($item->getAnswertext());
1297 $jitem[
'order'] = $item->getOrder();
1299 $jitem[
'lowerbound'] = $item->getLowerBound();
1300 $jitem[
'upperbound'] = $item->getUpperBound();
1302 $jitem[
'value'] = trim($jitem[
'value']);
1304 array_push($items, $jitem);
1312 $jgap[
'type'] = $gap->
getType();
1313 $jgap[
'item'] = $items;
1315 array_push($gaps, $jgap);
1317 $result[
'gaps'] = $gaps;
1319 $result[
'mobs'] = $mobs;
1320 return json_encode($result);
1345 $maxStep = $this->lookupMaxStep($active_id, $pass);
1347 $data = $this->db->queryF(
1349 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1350 FROM tst_solutions sol
1351 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1352 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s AND sol.step = %s
1353 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1355 [
"integer",
"integer",
"integer",
"integer"],
1356 [$active_id, $pass, $this->
getId(), $maxStep]
1359 $data = $this->db->queryF(
1361 SELECT sol.value1+1 as val, sol.value2, cloze.cloze_type
1362 FROM tst_solutions sol
1363 INNER JOIN qpl_a_cloze cloze ON cloze.gap_id = value1 AND cloze.question_fi = sol.question_fi
1364 WHERE sol.active_fi = %s AND sol.pass = %s AND sol.question_fi = %s
1365 GROUP BY sol.solution_id, sol.value1+1, sol.value2, cloze.cloze_type
1367 [
"integer",
"integer",
"integer"],
1368 [$active_id, $pass, $this->
getId()]
1372 while ($row = $this->db->fetchAssoc(
$data)) {
1373 if ($row[
"cloze_type"] == 1) {
1376 $result->addKeyValue($row[
"val"], $row[
"value2"]);
1379 $points = $this->calculateReachedPoints($active_id, $pass);
1380 $max_points = $this->getMaximumPoints();
1382 $result->setReachedPercentage(($points / $max_points) * 100);
1397 if ($index !==
null) {
1398 return $this->getGap($index);
1400 return $this->getGaps();
1409 $gap_used_in_combination = [];
1410 if ($assClozeGapCombinationObj->combinationExistsForQid($this->getId())) {
1411 $combinations_for_question = $assClozeGapCombinationObj->getCleanCombinationArray($this->
getId());
1414 foreach ($user_result as $user_result_build_list) {
1415 if (is_array($user_result_build_list)) {
1416 $gap_answers[$user_result_build_list[
'gap_id']] = $user_result_build_list[
'value'];
1420 foreach ($combinations_for_question as $combination) {
1421 foreach ($combination as $row_key => $row_answers) {
1422 $combination_fulfilled =
true;
1423 $points_for_combination = $row_answers[
'points'];
1424 foreach ($row_answers as $gap_key => $combination_gap_answer) {
1425 if ($gap_key !==
'points') {
1426 $gap_used_in_combination[$gap_key] = $gap_key;
1428 if ($combination_fulfilled && array_key_exists($gap_key, $gap_answers)) {
1429 switch ($combination_gap_answer[
'type']) {
1431 $is_text_gap_correct = $this->getTextgapPoints($gap_answers[$gap_key], $combination_gap_answer[
'answer'], 1);
1432 if ($is_text_gap_correct != 1) {
1433 $combination_fulfilled =
false;
1437 $answer = $this->gaps[$gap_key]->getItem($gap_answers[$gap_key]);
1438 $answertext = $answer?->getAnswertext();
1439 if ($answertext != $combination_gap_answer[
'answer']) {
1440 $combination_fulfilled =
false;
1444 $answer = $this->gaps[$gap_key]->getItem(0);
1445 if ($combination_gap_answer[
'answer'] !=
'out_of_bound') {
1446 $is_numeric_gap_correct = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1447 if ($is_numeric_gap_correct != 1) {
1448 $combination_fulfilled =
false;
1451 $wrong_is_the_new_right = $this->getNumericgapPoints($answer->getAnswertext(), $gap_answers[$gap_key], 1, $answer->getLowerBound(), $answer->getUpperBound());
1452 if ($wrong_is_the_new_right == 1) {
1453 $combination_fulfilled =
false;
1459 if ($gap_key !==
'points') {
1460 $combination_fulfilled =
false;
1464 if ($combination_fulfilled) {
1465 $points += $points_for_combination;
1470 return [$points, $gap_used_in_combination];
1481 $combinations[1] = [];
1482 if ($this->gap_combinations_exist) {
1483 $combinations = $this->calculateCombinationResult($user_result);
1484 $points = $combinations[0];
1487 $solution_values_text = [];
1488 $solution_values_select = [];
1489 $solution_values_numeric = [];
1490 foreach ($user_result as $gap_id => $value) {
1491 if (is_string($value)) {
1492 $value = [
"value" => $value];
1495 if (array_key_exists($gap_id, $this->gaps) && !array_key_exists($gap_id, $combinations[1])) {
1496 switch ($this->gaps[$gap_id]->getType()) {
1499 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1500 $answer = $this->gaps[$gap_id]->getItem($order);
1501 $gotpoints = $this->getTextgapPoints($answer->getAnswertext(), $value[
"value"], $answer->getPoints());
1502 if ($gotpoints > $gappoints) {
1503 $gappoints = $gotpoints;
1506 if (!$this->getIdenticalScoring()) {
1508 if ((in_array($value[
"value"], $solution_values_text)) && ($gappoints > 0.0)) {
1512 $points += $gappoints;
1513 $detailed[$gap_id] = [
"points" => $gappoints,
"best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ?
true :
false,
"positive" => ($gappoints > 0.0) ?
true :
false];
1514 array_push($solution_values_text, $value[
"value"]);
1518 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1519 $answer = $this->gaps[$gap_id]->getItem($order);
1520 $gotpoints = $this->getNumericgapPoints($answer->getAnswertext(), $value[
"value"], $answer->getPoints(), $answer->getLowerBound(), $answer->getUpperBound());
1521 if ($gotpoints > $gappoints) {
1522 $gappoints = $gotpoints;
1525 if (!$this->getIdenticalScoring()) {
1528 $eval->suppress_errors =
true;
1529 $found_value =
false;
1530 foreach ($solution_values_numeric as $solval) {
1531 if ($eval->e($solval) == $eval->e($value[
"value"])) {
1532 $found_value =
true;
1535 if ($found_value && ($gappoints > 0.0)) {
1539 $points += $gappoints;
1540 $detailed[$gap_id] = [
"points" => $gappoints,
"best" => ($this->getMaximumGapPoints($gap_id) == $gappoints) ?
true :
false,
"positive" => ($gappoints > 0.0) ?
true :
false];
1541 array_push($solution_values_numeric, $value[
"value"]);
1544 if ($value[
"value"] >= 0.0) {
1545 for ($order = 0; $order < $this->gaps[$gap_id]->getItemCount(); $order++) {
1546 $answer = $this->gaps[$gap_id]->getItem($order);
1547 if ($value[
"value"] == $answer->getOrder()) {
1548 $answerpoints = $answer->getPoints();
1549 if (!$this->getIdenticalScoring()) {
1551 if ((in_array($answer->getAnswertext(), $solution_values_select)) && ($answerpoints > 0.0)) {
1552 $answerpoints = 0.0;
1555 $points += $answerpoints;
1556 $detailed[$gap_id] = [
"points" => $answerpoints,
"best" => ($this->getMaximumGapPoints($gap_id) == $answerpoints) ?
true :
false,
"positive" => ($answerpoints > 0.0) ?
true :
false];
1557 array_push($solution_values_select, $answer->getAnswertext());
1573 if (!is_array($participant_session)) {
1577 $user_solution = [];
1579 foreach ($participant_session as $key => $val) {
1580 $user_solution[$key] = [
'gap_id' => $key,
'value' => $val];
1583 $reached_points = $this->calculateReachedPointsForSolution($user_solution);
1585 return $this->ensureNonNegativePoints($reached_points);
1592 foreach ($userSolution as $value1 => $value2) {
1593 if ($value1 == $gapIndex) {
1594 $answerValue = $value2;
1599 return $answerValue;
1604 $gap = $this->getGap($qIndex);
1610 foreach ($gap->
getItems($this->randomGroup->dontShuffle()) as $item) {
1611 if ($item->getAnswertext() === $answerOptionValue) {
1621 $gap = $this->getGap($qIndex);
1632 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
1633 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
1634 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
1635 AdditionalInformationGenerator::KEY_QUESTION_CLOZE_CLOZETEXT => $this->formatSAQuestion($this->getClozeText()),
1636 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
1638 AdditionalInformationGenerator::KEY_FEEDBACK => [
1639 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
1640 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
1645 foreach ($this->getGaps() as $gap_index => $gap) {
1647 foreach ($gap->
getItems($this->getShuffler()) as $item) {
1649 AdditionalInformationGenerator::KEY_QUESTION_REACHABLE_POINTS => $item->getPoints(),
1650 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION => $this->formatSAQuestion($item->getAnswertext()),
1651 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => $item->getOrder()
1654 $item_array[AdditionalInformationGenerator::KEY_QUESTION_LOWER_LIMIT] = $item->getLowerBound();
1655 $item_array[AdditionalInformationGenerator::KEY_QUESTION_UPPER_LIMIT] = $item->getUpperBound();
1657 array_push($items, $item_array);
1660 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_TEXTSIZE] = $gap->
getGapSize();
1661 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS] = $additional_info->
getTrueFalseTagForBool(
1664 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_CLOZE_GAP_TYPE] = $gap->
getType();
1665 $gap_array[AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTIONS] = $items;
1667 $gaps[$gap_index + 1] = $gap_array;
1669 $result[AdditionalInformationGenerator::KEY_QUESTION_CLOZE_GAPS] = $gaps;
1675 array $solution_values
1677 $parsed_solution = [];
1678 foreach ($this->getGaps() as $gap_index => $gap) {
1679 foreach ($solution_values as $solutionvalue) {
1680 if ($gap_index !== (
int) $solutionvalue[
'value1']) {
1685 $parsed_solution[$gap_index + 1] = $gap->
getItem($solutionvalue[
'value2'])->getAnswertext();
1689 $parsed_solution[$gap_index + 1] = $solutionvalue[
'value2'];
1692 return $parsed_solution;
1697 $parsed_solution = [];
1698 foreach ($this->getGaps() as $gap_index => $gap) {
1699 foreach ($solution_values as $solutionvalue) {
1700 if ($gap_index !== (
int) $solutionvalue[
'value1']) {
1705 $parsed_solution[] = $this->
lng->txt(
'gap') .
' ' . $gap_index + 1 .
': '
1706 . $gap->
getItem($solutionvalue[
'value2'])->getAnswertext();
1710 $parsed_solution[] = $this->
lng->txt(
'gap') .
' ' . $gap_index + 1 .
': '
1711 . $solutionvalue[
'value2'];
1714 return $parsed_solution;
1720 foreach ($this->getGaps() as $gap_index => $gap) {
1721 $correct_answers = array_map(
1722 fn(
int $v):
string => $gap->
getItem($v)->getAnswertext(),
1725 $answers[] = $this->
lng->txt(
'gap') .
' ' . $gap_index + 1 .
': '
1726 . 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)
saveToDb(?int $original_id=null)
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'))