19 require_once
'./Modules/Test/classes/inc.AssessmentConstants.php';
73 $this->textsize = self::DEFAULT_TEXT_SIZE;
83 if (mb_strlen($this->title)
112 $this->db->manipulateF(
113 "DELETE FROM qpl_a_errortext WHERE question_fi = %s",
119 foreach ($this->errordata as $error) {
120 $next_id = $this->db->nextId(
'qpl_a_errortext');
121 $this->db->manipulateF(
122 "INSERT INTO qpl_a_errortext (answer_id, question_fi, text_wrong, text_correct, points, sequence, position) VALUES (%s, %s, %s, %s, %s, %s, %s)",
123 [
'integer',
'integer',
'text',
'text',
'float',
'integer',
'integer'],
127 $error->getTextWrong(),
128 $error->getTextCorrect(),
131 $error->getPosition()
144 $this->db->manipulateF(
150 $this->db->manipulateF(
151 "INSERT INTO " . $this->
getAdditionalTableName() .
" (question_fi, errortext, parsed_errortext, textsize, points_wrong) VALUES (%s, %s, %s, %s, %s)",
152 [
"integer",
"text",
"text",
"float",
"float"],
171 $db_question = $this->db->queryF(
176 if ($db_question->numRows() === 1) {
177 $data = $this->db->fetchAssoc($db_question);
178 $this->
setId($question_id);
181 $this->
setComment((
string) $data[
"description"]);
189 $this->
setParsedErrorText(json_decode($data[
'parsed_errortext'] ?? json_encode([]),
true));
205 $db_error_text = $this->db->queryF(
206 "SELECT * FROM qpl_a_errortext WHERE question_fi = %s ORDER BY sequence ASC",
211 if ($db_error_text->numRows() > 0) {
212 while (
$data = $this->db->fetchAssoc($db_error_text)) {
214 (
string)
$data[
'text_wrong'],
215 (
string) $data[
'text_correct'],
216 (
float) $data[
'points'],
224 parent::loadFromDb($question_id);
232 $needs_finalizing =
false;
234 $needs_finalizing =
true;
238 if (isset($this->errordata[0])
239 && $this->errordata[0]->getPosition() === null) {
240 foreach ($this->errordata as
$key => $error) {
246 if ($needs_finalizing) {
257 if ($this->
id <= 0) {
262 $this_id = $this->
getId();
267 $original_id = $this->questioninfo->getOriginalId($this->
id);
270 if ((
int) $testObjId > 0) {
271 $clone->setObjId($testObjId);
291 $clone->copyPageOfQuestion($this_id);
293 $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
295 $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
304 if ($this->
getId() <= 0) {
305 throw new RuntimeException(
'The question has not been saved. It cannot be duplicated');
309 $thisId = $this->
getId();
314 $original_id = $this->questioninfo->getOriginalId($this->
id);
316 $clone->setObjId($target_questionpool_id);
327 $clone->onCopy($thisObjId, $thisId, $clone->getObjId(), $clone->getId());
334 if ($this->
getId() <= 0) {
335 throw new RuntimeException(
'The question has not been saved. It cannot be duplicated');
339 $sourceParentId = $this->
getObjId();
345 $clone->setObjId($targetParentId);
347 if ($targetQuestionTitle) {
348 $clone->setTitle($targetQuestionTitle);
353 $clone->copyPageOfQuestion($sourceQuestionId);
355 $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
357 $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
370 foreach ($this->errordata as $error) {
371 if ($error->getPoints() > 0) {
372 $maxpoints += $error->getPoints();
388 public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution =
true, $returndetails =
false): float
390 if ($returndetails) {
391 throw new ilTestException(
'return details not implemented for ' . __METHOD__);
396 if (is_null($pass)) {
401 while ($row = $this->db->fetchAssoc($result)) {
402 $positions[] = $row[
'value1'];
425 if (is_null($pass)) {
431 function () use ($selected, $active_id, $pass, $authorized) {
434 foreach ($selected as $position) {
453 private function logUserAction(
bool $user_entered_values,
int $active_id): void
455 $log_text = $this->
lng->txtlng(
457 $user_entered_values ?
'log_user_entered_values' :
'log_user_not_entered_values',
465 if (mb_strlen($_POST[
"qst_" . $this->
getId()])) {
466 return explode(
',', $_POST[
"qst_{$this->getId()}"]);
474 return 'assErrorText';
479 return 'qpl_qst_errortext';
484 return 'qpl_a_errortext';
492 parent::setExportDetailsXLSX($worksheet, $startrow, $col, $active_id, $pass);
497 if (is_array($solutions)) {
498 foreach ($solutions as $solution) {
499 $selections[] = $solution[
'value1'];
504 $worksheet->
setCell($startrow + $i, $col + 2, $errortext);
507 return $startrow + $i + 1;
510 public function fromXML($item,
int $questionpool_id, ?
int $tst_id, &$tst_object,
int &$question_counter, array $import_mapping, array &$solutionhints = []): array
513 return $import->fromXML($item, $questionpool_id, $tst_id, $tst_object, $question_counter, $import_mapping);
516 public function toXML($a_include_header =
true, $a_include_binary =
true, $a_shuffle =
false, $test_output =
false, $force_image_references =
false): string
519 return $export->toXML($a_include_header, $a_include_binary, $a_shuffle, $test_output, $force_image_references);
525 $this->errordata = [];
527 $has_too_long_errors =
false;
529 foreach ($paragraph as $position => $word) {
530 if ($word[
'error_type'] ===
'in_passage' 531 || $word[
'error_type'] ===
'passage_end' 532 || $word[
'error_type'] ===
'none') {
536 $text_wrong = $word[
'text_wrong'];
537 if (mb_strlen($text_wrong) > self::ERROR_MAX_LENGTH) {
538 $has_too_long_errors =
true;
548 if ($has_too_long_errors) {
549 $this->tpl->setOnScreenMessage(
551 $this->
lng->txt(
'qst_error_text_too_long')
559 foreach ($paragraph as $position => $word) {
560 if (isset($word[
'text_wrong'])
562 || mb_substr($word[
'text_wrong'], 0, -1) === $error->
getTextWrong()
563 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word[
'text_wrong'], -1)) === 1)
576 foreach ($this->errordata as $error) {
577 $position = $error->getPosition();
579 if (array_key_exists($position, $paragraph)) {
580 $this->parsed_errortext[
$key][$position][
'text_correct'] =
581 $error->getTextCorrect();
582 $this->parsed_errortext[
$key][$position][
'points'] =
596 $this->errordata = [];
598 foreach ($errors as $error) {
600 $this->errordata[] = $answer;
608 if ($error->getPosition() === null) {
609 unset($this->errordata[$index]);
612 $this->errordata = array_values($this->errordata);
621 array $current_error_data,
624 foreach ($current_error_data as $answer_object) {
625 if (strcmp($answer_object->getTextWrong(), $text_wrong) === 0) {
627 $answer_object->getTextCorrect(),
628 $answer_object->getPoints()
637 bool $graphical_output =
false,
638 bool $show_correct_solution =
false,
639 bool $use_link_tags =
true,
640 array $correctness_icons = []
644 $array_reduce_function = fn(?
string $carry,
int $position)
650 $show_correct_solution,
654 $output_array[] =
'<p>' . trim(array_reduce(array_keys($paragraph), $array_reduce_function)) .
'</p>';
657 return implode(
"\n", $output_array);
664 bool $graphical_output,
665 bool $show_correct_solution,
667 array $correctness_icons
687 bool $show_correct_solution
689 $v = $paragraph[$position];
690 if ($show_correct_solution ===
true 691 && ($v[
'error_type'] ===
'in_passage' 692 || $v[
'error_type'] ===
'passage_end')) {
695 if ($show_correct_solution
696 && ($v[
'error_type'] ===
'passage_start' 697 || $v[
'error_type'] ===
'word')) {
698 return $v[
'text_correct'] ??
'';
706 bool $show_correct_solution,
709 if ($show_correct_solution !==
true 710 && in_array($position, $selections[
'user'])) {
711 return 'ilc_qetitem_ErrorTextSelected';
714 if ($show_correct_solution ===
true 715 && in_array($position, $selections[
'best'])) {
716 return 'ilc_qetitem_ErrorTextSelected';
719 return 'ilc_qetitem_ErrorTextItem';
724 bool $graphical_output,
726 array $correctness_icons
728 if ($graphical_output ===
true 729 && (in_array($position, $selections[
'user']) && !in_array($position, $selections[
'best'])
730 || !in_array($position, $selections[
'user']) && in_array($position, $selections[
'best']))) {
731 return $correctness_icons[
'not_correct'];
734 if ($graphical_output ===
true 735 && in_array($position, $selections[
'user']) && in_array($position, $selections[
'best'])) {
736 return $correctness_icons[
'correct'];
744 if (!is_array($selections)) {
749 $array_reduce_function =
function ($carry, $k) use ($paragraph, $selections) {
750 $text = $paragraph[$k][
'text'];
751 if (in_array($k, $selections)) {
752 $text = self::ERROR_WORD_MARKER . $paragraph[$k][
'text'] . self::ERROR_WORD_MARKER;
754 return $carry .
' ' . $text;
756 $output_array[] = trim(array_reduce(array_keys($paragraph), $array_reduce_function));
758 return implode(
"\n", $output_array);
765 foreach ($positions_array as $position => $position_data) {
767 || $withPositivePointsOnly && $position_data[
'points'] <= 0) {
771 $selections[] = $position;
772 if ($position_data[
'length'] > 1) {
773 for ($i = 1;$i < $position_data[
'length'];$i++) {
774 $selections[] = $position + $i;
791 foreach ($correct_positions as $correct_position => $correct_position_data) {
792 $selected_word_key = array_search($correct_position, $selected_word_positions);
793 if ($selected_word_key ===
false) {
797 if ($correct_position_data[
'length'] === 1) {
798 $points += $correct_position_data[
'points'];
799 unset($selected_word_positions[$selected_word_key]);
803 $passage_complete =
true;
804 for ($i = 1;$i < $correct_position_data[
'length'];$i++) {
805 $selected_passage_element_key = array_search($correct_position + $i, $selected_word_positions);
806 if ($selected_passage_element_key ===
false) {
807 $passage_complete =
false;
810 unset($selected_word_positions[$selected_passage_element_key]);
813 if ($passage_complete) {
814 $points += $correct_position_data[
'points'];
815 unset($selected_word_positions[$selected_word_key]);
819 foreach ($selected_word_positions as $word_position) {
820 if (!array_key_exists($word_position, $correct_positions)) {
831 $this->errordata = [];
849 $correct_answers = [];
850 foreach ($this->
getErrorData() as $index => $answer_obj) {
851 $correct_answers[] = [
852 'answertext_wrong' => $answer_obj->getTextWrong(),
853 'answertext_correct' => $answer_obj->getTextCorrect(),
854 'points' => $answer_obj->getPoints(),
855 'length' => $answer_obj->getLength(),
856 'pos' => $this->
getId() .
'_' . $answer_obj->getPosition()
859 return $correct_answers;
864 return $this->errortext ??
'';
869 $this->errortext = $text ??
'';
880 foreach ($this->parsed_errortext as $paragraph) {
881 foreach ($paragraph as $position => $word) {
883 'answertext' => $word[
'text'],
884 'order' => $this->
getId() .
'_' . $position
888 'answertext' =>
'###' 909 if ($a_value === null) {
912 $this->textsize = $a_value;
922 $this->points_wrong = $a_value;
928 $result[
'id'] = $this->
getId();
935 $result[
'feedback'] = [
936 'onenotcorrect' => $this->
formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
937 'allcorrect' => $this->
formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
944 $result[
'mobs'] = $mobs;
946 return json_encode($result);
986 $data = $this->db->queryF(
987 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = ( 988 SELECT MAX(step) FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s 990 [
"integer",
"integer",
"integer",
"integer",
"integer",
"integer"],
991 [$active_id, $pass, $this->
getId(), $active_id, $pass, $this->
getId()]
994 while ($row = $this->db->fetchAssoc(
$data)) {
995 $result->addKeyValue($row[
"value1"], $row[
"value1"]);
1001 $result->setReachedPercentage((
$points / $max_points) * 100);
1008 $text_by_paragraphs = preg_split(self::PARAGRAPH_SPLIT_REGEXP, $this->
getErrorText());
1011 foreach ($text_by_paragraphs as $paragraph) {
1013 preg_split(self::WORD_SPLIT_REGEXP, trim($paragraph)),
1016 $offset += count(end($text_array));
1028 $paragraph_with_error_info = [];
1029 $passage_start = null;
1030 foreach ($paragraph as $position => $word) {
1031 $actual_position = $position + $offset;
1032 if ($passage_start !== null
1033 && (mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS[
'end']) === mb_strlen($word) - 2
1034 || mb_strrpos($word, self::ERROR_PARAGRAPH_DELIMITERS[
'end']) === mb_strlen($word) - 3
1035 && preg_match(self::FIND_PUNCTUATION_REGEXP, mb_substr($word, -1)) === 1)) {
1038 $paragraph_with_error_info[$passage_start][
'text_wrong'] .=
1040 $paragraph_with_error_info[$actual_position] = [
1041 'text' => $actual_word,
1042 'error_type' =>
'passage_end' 1044 $passage_start = null;
1047 if ($passage_start !== null) {
1048 $paragraph_with_error_info[$passage_start][
'text_wrong'] .=
' ' . $word;
1049 $paragraph_with_error_info[$actual_position] = [
1051 'error_type' =>
'in_passage' 1055 if (mb_strpos($word, self::ERROR_PARAGRAPH_DELIMITERS[
'start']) === 0) {
1056 $paragraph_with_error_info[$actual_position] = [
1057 'text' => substr($word, 2),
1058 'text_wrong' => substr($word, 2),
1059 'error_type' =>
'passage_start',
1060 'error_position' => $actual_position,
1062 $passage_start = $actual_position;
1065 if (mb_strpos($word, self::ERROR_WORD_MARKER) === 0) {
1066 $paragraph_with_error_info[$actual_position] = [
1067 'text' => substr($word, 1),
1068 'text_wrong' => substr($word, 1),
1069 'error_type' =>
'word',
1070 'error_position' => $actual_position,
1075 $paragraph_with_error_info[$actual_position] = [
1077 'error_type' =>
'none',
1082 return $paragraph_with_error_info;
1087 if (mb_substr($word, -2) === self::ERROR_PARAGRAPH_DELIMITERS[
'end']) {
1088 return mb_substr($word, 0, -2);
1090 return mb_substr($word, 0, -3) . mb_substr($word, -1);
1102 $error_text_array = array_reduce(
1103 $this->parsed_errortext,
1104 fn(
$c, $v) =>
$c + $v
1107 if ($index === null) {
1108 return $error_text_array;
1111 if (array_key_exists($index, $error_text_array)) {
1112 return $error_text_array[$index];
1120 $array_by_position = [];
1121 foreach ($this->errordata as $error) {
1122 $array_by_position[$error->getPosition()] = [
1123 'length' => $error->getLength(),
1124 'points' => $error->getPoints(),
1125 'text' => $error->getTextWrong(),
1126 'text_correct' => $error->getTextCorrect()
1129 ksort($array_by_position);
1130 return $array_by_position;
getPointsForSelectedPositions(array $selected_word_positions)
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...
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
const FIND_PUNCTUATION_REGEXP
setNrOfTries(int $a_nr_of_tries)
static getInstance($identifier)
const PercentageResultExpression
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const NumberOfResultExpression
const ExclusiveResultExpression
Abstract basic class which is to be extended by the concrete assessment question type classes...
loadFromDb($question_id)
Loads the object from the database.
addErrorInformationToTextParagraphArray(array $paragraph, int $offset)
toXML($a_include_header=true, $a_include_binary=true, $a_shuffle=false, $test_output=false, $force_image_references=false)
saveWorkingData($active_id, $pass=null, $authorized=true)
Saves the learners input of the question to the database.
ensureNonNegativePoints($points)
saveToDb($original_id="")
Saves a the object to the database.
getClassForPosition(int $position, bool $show_correct_solution, array $selections)
getTextForPosition(int $position, array $paragraph, bool $show_correct_solution)
copyObject($target_questionpool_id, $title="")
Copies an object.
getErrorDataAsArrayForJS()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getParticipantsSolution()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setComment(string $comment="")
__construct( $title='', $comment='', $author='', $owner=-1, $question='')
assErorText constructor
float $points
The maximum available points for the question.
const ERROR_PARAGRAPH_DELIMITERS
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
getUserQuestionResult($active_id, $pass)
Get the user solution for a question by active_id and the test pass.
Base Exception for all Exceptions relating to Modules/Test.
getCorrectnessIconForPosition(int $position, bool $graphical_output, array $selections, array $correctness_icons)
setParticipantsSolution($participantSolution)
generateArrayByPositionFromErrorData()
duplicate(bool $for_test=true, string $title="", string $author="", int $owner=-1, $testObjId=null)
Duplicates the object.
removeErrorDataWithoutPosition()
setErrorData(array $errors)
getParsedErrorTextForJS()
isComplete()
Returns true, if a single choice question is complete for use.
createErrorTextExport(array $selections)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
static _enabledAssessmentLogging()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
parsePassageEndWord(string $word)
logUserAction(bool $user_entered_values, int $active_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
assembleErrorTextOutput(array $selections, bool $graphical_output=false, bool $show_correct_solution=false, bool $use_link_tags=true, array $correctness_icons=[])
fromXML($item, int $questionpool_id, ?int $tst_id, &$tst_object, int &$question_counter, array $import_mapping, array &$solutionhints=[])
static logAction(string $logtext, int $active_id, int $question_id)
const PARAGRAPH_SPLIT_REGEXP
getOperators($expression)
Get all available operations for a specific question.
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...
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
getExpressionTypes()
Get all available expression types for a specific question.
setErrorText(?string $text)
addPositionToErrorAnswer(assAnswerErrorText $error)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
string $question
The question text.
generateOutputStringFromPosition(int $position, array $selections, array $paragraph, bool $graphical_output, bool $show_correct_solution, bool $use_link_tags, array $correctness_icons)
static getOperatorsByExpression($expression)
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
deductHintPointsFromReachedPoints(ilAssQuestionPreviewSession $previewSession, $reachedPoints)
calculateReachedPoints($active_id, $pass=null, $authorizedSolution=true, $returndetails=false)
Returns the points, a learner has reached answering the question.
saveQuestionDataToDb(int $original_id=-1)
getSolutionMaxPass(int $active_id)
correctDataAfterParserUpdate()
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle="")
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getBestSelection($withPositivePointsOnly=true)
setOriginalId(?int $original_id)
setTitle(string $title="")
setErrorsFromParsedErrorText()
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
saveAdditionalQuestionDataToDb()
Saves the data for the additional data table.
completeParsedErrorTextFromErrorData()
withPosition(int $position)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
setAuthor(string $author="")
setParsedErrorText(array $parsed_errortext)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
getAdditionalInformationFromExistingErrorDataByErrorText(array $current_error_data, string $text_wrong)
static getDraftInstance()
setExportDetailsXLSX(ilAssExcelFormatHelper $worksheet, int $startrow, int $col, int $active_id, int $pass)
{}
getErrorTokenHtml($item, $class, $useLinkTags)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setQuestion(string $question="")
const EmptyAnswerExpression