19declare(strict_types=1);
23use ILIAS\TestQuestionPool\ManipulateImagesInChoiceQuestionsTrait;
34 use ManipulateImagesInChoiceQuestionsTrait;
69 $answer->setPosition($i);
70 $this->answers[$answer->getPosition()] = $answer;
76 return 'assKprimChoice';
81 return "qpl_qst_kprim";
175 $answer->setAnswertext(
180 $this->answers = array_map($clean_answer_text,
$answers);
194 if ($answer->getPosition() == $position) {
207 $this->answers[] = $answer;
214 while (
$data = $this->db->fetchAssoc(
$res)) {
215 $this->
setId($questionId);
236 if (is_numeric(
$data[
'thumb_size'])) {
244 if (
$data[
'custom_true'] !==
null) {
248 if (
$data[
'custom_false'] !==
null) {
252 if (
$data[
'score_partsol'] !==
null) {
256 if (isset(
$data[
'feedback_setting'])) {
274 parent::loadFromDb($questionId);
279 $res = $this->db->queryF(
280 "SELECT * FROM {$this->getAnswerTableName()} WHERE question_fi = %s ORDER BY position ASC",
285 while (
$data = $this->db->fetchAssoc(
$res)) {
288 $answer->setPosition(
$data[
'position']);
292 $answer->setImageFile(
$data[
'imagefile']);
293 $answer->setThumbPrefix($this->getThumbPrefix());
297 $answer->setCorrectness(
$data[
'correctness']);
299 $this->answers[$answer->getPosition()] = $answer;
317 'question_fi' => [
'integer', $this->
getId()]
338 'question_fi' => [
'integer', $this->
getId()],
339 'position' => [
'integer', (
int) $answer->getPosition()]
342 'answertext' => [
'text', $answer->getAnswertext()],
343 'imagefile' => [
'text', $answer->getImageFile()],
344 'correctness' => [
'integer', (
int) $answer->getCorrectness()]
352 foreach ([$this->title, $this->author, $this->question] as $text) {
353 if (!strlen($text)) {
358 if (!isset($this->points)) {
365 if (is_null($answer->getCorrectness())) {
370 (!is_string($answer->getAnswertext()) || $answer->getAnswertext() ===
'') &&
371 (!is_string($answer->getImageFile()) || $answer->getImageFile() ===
'')
383 bool $authorized =
true
385 if ($pass === null) {
391 function () use ($answer, $active_id, $pass, $authorized) {
393 foreach ($answer as $index => $value) {
394 if ($value !==
null) {
407 bool $authorized_solution =
true
410 if (is_null($pass)) {
411 $pass = $this->getSolutionMaxPass($active_id);
414 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
416 while (
$data = $this->db->fetchAssoc($result)) {
417 $found_values[(
int)
$data[
'value1']] = (
int)
$data[
'value2'];
420 $points = $this->calculateReachedPointsForSolution($found_values, $active_id);
427 return [self::ANSWER_TYPE_SINGLE_LINE, self::ANSWER_TYPE_MULTI_LINE];
432 $validTypes = $this->getValidAnswerTypes();
433 return in_array($answerType, $validTypes);
448 self::ANSWER_TYPE_SINGLE_LINE =>
$lng->txt(
'answers_singleline'),
449 self::ANSWER_TYPE_MULTI_LINE =>
$lng->txt(
'answers_multiline')
456 self::OPTION_LABEL_RIGHT_WRONG,
457 self::OPTION_LABEL_PLUS_MINUS,
458 self::OPTION_LABEL_APPLICABLE_OR_NOT,
459 self::OPTION_LABEL_ADEQUATE_OR_NOT,
460 self::OPTION_LABEL_CUSTOM
467 $this->getValidOptionLabels(),
468 function (array
$c,
string $option_label) use (
$lng): array {
469 $c[$option_label] =
$lng->txt($this->getLangVarForOptionLabel($option_label));
478 return match ($option_label) {
479 self::OPTION_LABEL_RIGHT_WRONG =>
'option_label_right_wrong',
480 self::OPTION_LABEL_PLUS_MINUS =>
'option_label_plus_minus',
481 self::OPTION_LABEL_APPLICABLE_OR_NOT =>
'option_label_applicable_or_not',
482 self::OPTION_LABEL_ADEQUATE_OR_NOT =>
'option_label_adequate_or_not',
483 self::OPTION_LABEL_CUSTOM =>
'option_label_custom'
489 $valid_labels = $this->getValidOptionLabels();
490 return in_array($option_label, $valid_labels);
495 if ($option_label === self::OPTION_LABEL_CUSTOM) {
496 return $this->getCustomTrueOptionLabel();
500 $this->getTrueOptionLabel($option_label)
506 switch ($option_label) {
507 case self::OPTION_LABEL_RIGHT_WRONG:
508 return 'option_label_right';
510 case self::OPTION_LABEL_PLUS_MINUS:
511 return 'option_label_plus';
513 case self::OPTION_LABEL_APPLICABLE_OR_NOT:
514 return 'option_label_applicable';
516 case self::OPTION_LABEL_ADEQUATE_OR_NOT:
517 return 'option_label_adequate';
520 throw new \ErrorException(
'Invalide Option Label');
526 if ($option_label === self::OPTION_LABEL_CUSTOM) {
527 return $this->getCustomFalseOptionLabel();
531 $this->getFalseOptionLabel($option_label)
537 switch ($option_label) {
538 case self::OPTION_LABEL_RIGHT_WRONG:
539 return 'option_label_wrong';
541 case self::OPTION_LABEL_PLUS_MINUS:
542 return 'option_label_minus';
544 case self::OPTION_LABEL_APPLICABLE_OR_NOT:
545 return 'option_label_not_applicable';
547 case self::OPTION_LABEL_ADEQUATE_OR_NOT:
548 return 'option_label_not_adequate';
551 throw new \ErrorException(
'Invalide Option Label');
558 $lng->txt(
'kprim_instruction_text'),
559 $this->getTrueOptionLabelTranslation(
$lng, $option_label),
560 $this->getFalseOptionLabelTranslation(
$lng, $option_label)
566 return $labelValue == self::OPTION_LABEL_CUSTOM;
571 foreach ($answers as $answer) {
574 if (!isset($files[$answer->getPosition()])) {
578 $this->handleFileUpload($answer, $files[$answer->getPosition()]);
584 $imagePath = $this->getImagePath();
586 if (!file_exists($imagePath)) {
590 $filename = $this->buildHashedImageFilename($fileData[
'name'],
true);
599 $this->generateThumbForFile(
$filename, $this->getImagePath(), $this->getThumbSize());
606 $answer = $this->getAnswer($position);
608 if (file_exists($answer->getImageFsPath())) {
612 if (file_exists($answer->getThumbFsPath())) {
616 $answer->setImageFile(
null);
621 $solutionSubmit = [];
622 $post = $this->dic->http()->wrapper()->post();
624 foreach ($this->getAnswers() as $index =>
$a) {
625 if (
$post->has(
"kprim_choice_result_$index")) {
626 $value =
$post->retrieve(
627 "kprim_choice_result_$index",
628 $this->dic->refinery()->kindlyTo()->string()
630 if (is_numeric($value)) {
631 $solutionSubmit[] = $value;
634 $solutionSubmit[] =
null;
637 return $solutionSubmit;
643 if ($found_values ===
null) {
646 foreach ($this->getAnswers() as $answer) {
647 if (!isset($found_values[$answer->getPosition()])) {
651 if ($found_values[$answer->getPosition()] == $answer->getCorrectness()) {
656 if ($numCorrect >= self::NUM_REQUIRED_ANSWERS) {
657 $points = $this->getPoints();
658 } elseif ($this->isScorePartialSolutionEnabled() && $numCorrect >= self::PARTIAL_SCORING_NUM_CORRECT_ANSWERS) {
659 $points = $this->getPoints() / 2;
665 if (count($found_values) == 0) {
669 return (
float) $points;
687 $combinedText = parent::getRTETextWithMediaObjects();
689 foreach ($this->getAnswers() as $answer) {
690 $combinedText .= $answer->getAnswertext();
693 return $combinedText;
701 foreach ($this->getAnswers() as $answer) {
712 $this->
lng->loadLanguageModule(
'assessment');
715 $result[
'id'] = $this->
getId();
716 $result[
'type'] = $this->getQuestionType();
717 $result[
'title'] = $this->getTitleForHTMLOutput();
718 $result[
'question'] = $this->formatSAQuestion($this->getQuestion());
719 $result[
'instruction'] = $this->getInstructionTextTranslation(
721 $this->getOptionLabel()
723 $result[
'nr_of_tries'] = $this->getNrOfTries();
724 $result[
'shuffle'] = $this->isShuffleAnswersEnabled();
725 $result[
'feedback'] = [
726 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
727 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
730 $result[
'trueOptionLabel'] = $this->getTrueOptionLabelTranslation($this->
lng, $this->getOptionLabel());
731 $result[
'falseOptionLabel'] = $this->getFalseOptionLabelTranslation($this->
lng, $this->getOptionLabel());
733 $result[
'num_allowed_failures'] = $this->getNumAllowedFailures();
738 foreach ($this->getAnswers() as $key => $answer) {
739 if (strlen((
string) $answer->getImageFile())) {
744 'answertext' => $this->formatSAQuestion($answer->getAnswertext() ??
''),
745 'correctness' => (bool) $answer->getCorrectness(),
746 'order' => (
int) $answer->getPosition(),
747 'image' => (string) $answer->getImageFile(),
748 'feedback' => $this->formatSAQuestion(
749 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
754 $result[
'answers'] = $answers;
757 $result[
'path'] = $this->getImagePathWeb();
758 $result[
'thumb'] = $this->getThumbSize();
762 $result[
'mobs'] = $mobs;
764 return json_encode($result);
769 if ($this->isScorePartialSolutionEnabled()) {
770 return self::NUM_REQUIRED_ANSWERS - self::PARTIAL_SCORING_NUM_CORRECT_ANSWERS;
778 return 'feedback_correct_kprim';
783 if ($position < 0 || $position >= (self::NUM_REQUIRED_ANSWERS - 1)) {
787 for ($i = 0, $max = count($this->answers); $i < $max; $i++) {
788 if ($i == $position) {
789 $movingAnswer = $this->answers[$i];
790 $targetAnswer = $this->answers[ $i + 1 ];
792 $movingAnswer->setPosition($position + 1);
793 $targetAnswer->setPosition($position);
795 $this->answers[ $i + 1 ] = $movingAnswer;
796 $this->answers[$i] = $targetAnswer;
804 if ($position <= 0 || $position > (self::NUM_REQUIRED_ANSWERS - 1)) {
808 for ($i = 0, $max = count($this->answers); $i < $max; $i++) {
809 if ($i == $position) {
810 $movingAnswer = $this->answers[$i];
811 $targetAnswer = $this->answers[ $i - 1 ];
813 $movingAnswer->setPosition($position - 1);
814 $targetAnswer->setPosition($position);
816 $this->answers[ $i - 1 ] = $movingAnswer;
817 $this->answers[$i] = $targetAnswer;
827 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
828 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
829 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
830 AdditionalInformationGenerator::KEY_QUESTION_KPRIM_OPTION_LABEL => $additional_info
831 ->
getTagForLangVar($this->getLangVarForOptionLabel($this->getOptionLabel())),
832 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
834 AdditionalInformationGenerator::KEY_FEEDBACK => [
835 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
836 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
840 $result[AdditionalInformationGenerator::KEY_QUESTION_KPRIM_SCORE_PARTIAL_SOLUTION_ENABLED] = $additional_info
844 foreach ($this->getAnswers() as $key => $answer) {
845 $answers[$key + 1] = [
846 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION => $this->formatSAQuestion($answer->getAnswertext() ??
''),
847 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_CORRECTNESS => $additional_info->
getTrueFalseTagForBool((
bool) $answer->getCorrectness()),
848 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => (
int) $answer->getPosition(),
849 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_IMAGE => (string) $answer->getImageFile(),
850 AdditionalInformationGenerator::KEY_FEEDBACK => $this->formatSAQuestion(
851 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
856 $result[AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTIONS] = $answers;
863 array $solution_values
865 $parsed_solution = [];
866 $true_option_label = $this->getOptionLabel() === self::OPTION_LABEL_CUSTOM
867 ? $this->getCustomTrueOptionLabel()
868 : $this->getTrueOptionLabel($this->getOptionLabel());
869 $false_option_label = $this->getOptionLabel() === self::OPTION_LABEL_CUSTOM
870 ? $this->getCustomFalseOptionLabel()
871 : $this->getFalseOptionLabel($this->getOptionLabel());
872 foreach ($this->getAnswers() as
$id => $answer) {
874 foreach ($solution_values as $solution) {
875 if ($solution[
'value1'] !== (
string)
$id) {
879 $value = $false_option_label;
880 if ($solution[
'value2'] ===
'1') {
881 $value = $true_option_label;
885 $parsed_solution[$answer->getAnswertext()] = $value;
887 return $parsed_solution;
892 $parsed_solution = [];
893 $true_option_label = $this->getTrueOptionLabelTranslation($this->
lng, $this->getOptionLabel());
894 $false_option_label = $this->getFalseOptionLabelTranslation($this->
lng, $this->getOptionLabel());
895 foreach ($this->getAnswers() as
$id => $answer) {
896 $value = $this->
lng->txt(
'none');
897 foreach ($solution_values as $solution) {
898 if ($solution[
'value1'] !== (
string)
$id) {
902 $value = $false_option_label;
903 if ($solution[
'value2'] ===
'1') {
904 $value = $true_option_label;
908 $parsed_solution[] =
"{$answer->getAnswertext()} ({$value})";
910 return $parsed_solution;
915 $true_option_label = $this->getTrueOptionLabelTranslation($this->
lng, $this->getOptionLabel());
916 $false_option_label = $this->getFalseOptionLabelTranslation($this->
lng, $this->getOptionLabel());
919 .
' (' . $v->
getCorrectness() ? $true_option_label : $false_option_label .
')',
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
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.
getSpecificFeedbackAllCorrectOptionLabel()
const PARTIAL_SCORING_NUM_CORRECT_ANSWERS
getRTETextWithMediaObjects()
toJSON()
Returns a JSON representation of the question.
const OPTION_LABEL_RIGHT_WRONG
isSingleLineAnswerType($answerType)
setCustomTrueOptionLabel($customTrueOptionLabel)
bool $shuffle_answers_enabled
handleFileUpload(ilAssKprimChoiceAnswer $answer, $fileData)
const ANSWER_TYPE_MULTI_LINE
handleFileUploads($answers, $files)
setSpecificFeedbackSetting(int $specific_feedback_setting)
setShuffleAnswersEnabled(bool $shuffle_answers_enabled)
getLangVarForOptionLabel(string $option_label)
setCustomFalseOptionLabel($customFalseOptionLabel)
const NUM_REQUIRED_ANSWERS
string $customFalseOptionLabel
getFalseOptionLabelTranslation(ilLanguage $lng, string $option_label)
setOptionLabel(string $option_label)
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
const ANSWER_TYPE_SINGLE_LINE
isScorePartialSolutionEnabled()
addAnswer(ilAssKprimChoiceAnswer $answer)
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
removeAnswerImage($position)
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
cloneQuestionTypeSpecificProperties(\assQuestion $target)
const OPTION_LABEL_ADEQUATE_OR_NOT
const OPTION_LABEL_CUSTOM
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
getFalseOptionLabel(string $option_label)
int $specific_feedback_setting
getValidOptionLabelsTranslated(ilLanguage $lng)
getTrueOptionLabel(string $option_label)
getCustomFalseOptionLabel()
setAnswerType($answerType)
string $customTrueOptionLabel
getInstructionTextTranslation(ilLanguage $lng, $option_label)
const OPTION_LABEL_PLUS_MINUS
loadAnswerData(int $question_id)
getTrueOptionLabelTranslation(ilLanguage $lng, string $option_label)
isCustomOptionLabel($labelValue)
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
saveToDb(?int $original_id=null)
isValidAnswerType($answerType)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
getCorrectSolutionForTextOutput(int $active_id, int $pass)
isShuffleAnswersEnabled()
calculateReachedPointsForSolution(?array $found_values, int $active_id=0)
moveAnswerDown($position)
getSpecificFeedbackSetting()
isValidOptionLabel(?string $option_label)
getAnswerTypeSelectOptions(ilLanguage $lng)
const OPTION_LABEL_APPLICABLE_OR_NOT
__construct($title='', $comment='', $author='', $owner=-1, $question='')
setScorePartialSolutionEnabled($scorePartialSolutionEnabled)
setThumbSize(int $thumbSize)
getCustomTrueOptionLabel()
bool $scorePartialSolutionEnabled
setOriginalId(?int $original_id)
getHtmlQuestionContentPurifier()
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
setQuestion(string $question="")
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
setAuthor(string $author="")
setComment(string $comment="")
setLastChange(int $lastChange)
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
setTitle(string $title="")
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
saveQuestionDataToDb(?int $original_id=null)
getImagePathWeb()
Returns the web image path for web accessable images of a question.
const FEEDBACK_SETTING_ALL
setAnswertext($answertext)
setImageFsDir($imageFsDir)
setImageFile(?string $imageFile)
static getDraftInstance()
static getInstance($identifier)
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
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...
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
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
if(!file_exists('../ilias.ini.php'))