19declare(strict_types=1);
23use ILIAS\TestQuestionPool\ManipulateImagesInChoiceQuestionsTrait;
44 use ManipulateImagesInChoiceQuestionsTrait;
79 private int $output_type = self::OUTPUT_ORDER
83 $this->shuffle =
true;
98 return $this->title !==
''
99 && $this->author !==
''
100 && $this->question !==
''
115 $result = $this->db->queryF(
120 if ($result->numRows() == 1) {
121 $data = $this->db->fetchAssoc($result);
122 $this->
setId($question_id);
137 $this->is_singleline =
$data[
'allow_images'] ===
null ||
$data[
'allow_images'] ===
'0';
138 $this->lastChange =
$data[
'tstamp'];
140 if (isset(
$data[
'feedback_setting'])) {
141 $this->feedback_setting =
$data[
'feedback_setting'];
156 $result = $this->db->queryF(
157 "SELECT * FROM qpl_a_mc WHERE question_fi = %s ORDER BY aorder ASC",
161 if ($result->numRows() > 0) {
162 while (
$data = $this->db->fetchAssoc($result)) {
164 if (!file_exists($imagefilename)) {
165 $data[
"imagefile"] =
null;
175 $answer->setPointsUnchecked(
$data[
"points_unchecked"]);
176 $answer->setImage(
$data[
"imagefile"] ?
$data[
"imagefile"] :
null);
177 array_push($this->answers, $answer);
181 parent::loadFromDb($question_id);
212 string $answertext =
'',
214 float $points_unchecked = 0.0,
216 ?
string $answerimage =
null,
219 if (array_key_exists($order, $this->answers)) {
222 $this->getHtmlQuestionContentPurifier()->purify($answertext),
228 $answer->setPointsUnchecked($points_unchecked);
229 $answer->setImage($answerimage);
231 for ($i = 0; $i < $order; $i++) {
232 $newchoices[] = $this->answers[$i];
234 $newchoices[] = $answer;
235 for ($i = $order, $iMax = count($this->answers); $i < $iMax; $i++) {
236 $changed = $this->answers[$i];
237 $changed->setOrder($i + 1);
238 $newchoices[] = $changed;
240 $this->answers = $newchoices;
244 $this->getHtmlQuestionContentPurifier()->purify($answertext),
246 count($this->answers),
250 $answer->setPointsUnchecked($points_unchecked);
251 $answer->setImage($answerimage);
252 $this->answers[] = $answer;
264 return count($this->answers);
280 if (count($this->answers) < 1) {
283 if ($index >= count($this->answers)) {
287 return $this->answers[$index];
302 if (count($this->answers) < 1) {
305 if ($index >= count($this->answers)) {
308 $answer = $this->answers[$index];
309 if ($answer->hasImage()) {
310 $this->deleteImage($answer->getImage());
312 unset($this->answers[$index]);
313 $this->answers = array_values($this->answers);
314 for ($i = 0, $iMax = count($this->answers); $i < $iMax; $i++) {
315 if ($this->answers[$i]->
getOrder() > $index) {
316 $this->answers[$i]->setOrder($i);
338 $total_max_points = 0.0;
339 foreach ($this->getAnswers() as $answer) {
340 $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked());
342 return $total_max_points;
348 bool $authorized_solution =
true
351 if ($pass ===
null) {
352 $pass = $this->getSolutionMaxPass($active_id);
354 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
355 while (
$data = $this->db->fetchAssoc($result)) {
356 if (
$data[
'value1'] !==
'') {
357 array_push($found_values,
$data[
'value1']);
361 return $this->calculateReachedPointsForSolution($found_values, $active_id);
366 $submit = $this->getSolutionSubmit();
368 if ($this->getSelectionLimit()) {
369 if (count($submit) > $this->getSelectionLimit()) {
370 $failureMsg = sprintf(
371 $this->
lng->txt(
'ass_mc_sel_lim_exhausted_hint'),
372 $this->getSelectionLimit(),
373 $this->getAnswerCount()
376 $this->tpl->setOnScreenMessage(
'failure', $failureMsg,
true);
386 $tst_force_form_diff_input = $this->questionpool_request->strArray(
'tst_force_form_diff_input');
387 return !count($solutionSubmit) && !empty($tst_force_form_diff_input);
393 bool $authorized =
true
395 $pass = $pass ??
ilObjTest::_getPass($active_id);
397 $answer = $this->getSolutionSubmit();
398 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
399 function () use ($answer, $active_id, $pass, $authorized) {
400 $this->removeCurrentSolution($active_id, $pass, $authorized);
402 foreach ($answer as $value) {
404 $this->saveCurrentSolution($active_id, $pass, $value,
null, $authorized);
409 if ($this->isForcedEmptySolution($answer)) {
410 $this->saveCurrentSolution($active_id, $pass,
'mc_none_above',
null, $authorized);
421 if (!$this->is_singleline) {
427 $this->getAdditionalTableName(),
429 'shuffle' => [
'text', $this->getShuffle()],
430 'allow_images' => [
'text', $this->is_singleline ? 0 : 1],
431 'thumb_size' => [
'integer', $this->getThumbSize()],
432 'selection_limit' => [
'integer', $this->getSelectionLimit()],
433 'feedback_setting' => [
'integer', $this->getSpecificFeedbackSetting()]
435 [
'question_fi' => [
'integer', $this->
getId()]]
442 $result = $this->db->queryF(
443 "SELECT * FROM qpl_fb_specific WHERE question_fi = %s",
447 $db_feedback = $this->db->fetchAll($result);
450 if (
sizeof($db_feedback) >= 1 && $this->getAdditionalContentEditingMode() ==
'default') {
452 $result = $this->db->queryF(
453 "SELECT answer_id, aorder FROM qpl_a_mc WHERE question_fi = %s",
457 $db_answers = $this->db->fetchAll($result);
460 $post_answer_order_for_id = [];
461 foreach ($this->answers as $answer) {
463 if ($answer->getId() !==
null && !in_array($answer->getId(), array_keys($post_answer_order_for_id))) {
465 if ($answer->getId() == -1) {
468 $post_answer_order_for_id[$answer->getId()] = $answer->getOrder();
474 if (
sizeof($post_answer_order_for_id) >= 1) {
475 $db_answer_order_for_id = [];
476 $db_answer_id_for_order = [];
477 foreach ($db_answers as $db_answer) {
478 $db_answer_order_for_id[intval($db_answer[
'answer_id'])] = intval($db_answer[
'aorder']);
479 $db_answer_id_for_order[intval($db_answer[
'aorder'])] = intval($db_answer[
'answer_id']);
485 $db_answer_ids = array_keys($db_answer_order_for_id);
486 $post_answer_ids = array_keys($post_answer_order_for_id);
487 $diff_db_post_answer_ids = array_diff($db_answer_ids, $post_answer_ids);
488 $unused_answer_ids = array_keys($diff_db_post_answer_ids);
491 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($this->
getId(),
false);
493 foreach ($db_feedback as $feedback_option) {
495 if (in_array(intval($feedback_option[
'answer']), $unused_answer_ids)) {
500 $feedback_order_db = intval($feedback_option[
'answer']);
501 $db_answer_id = $db_answer_id_for_order[$feedback_order_db] ??
null;
505 if (is_null($db_answer_id) || $db_answer_id < 0) {
508 $feedback_order_post = $post_answer_order_for_id[$db_answer_id];
509 $feedback_option[
'answer'] = $feedback_order_post;
512 $next_id = $this->db->nextId(
'qpl_fb_specific');
513 $this->db->manipulateF(
514 "INSERT INTO qpl_fb_specific (feedback_id, question_fi, answer, tstamp, feedback, question)
515 VALUES (%s, %s, %s, %s, %s, %s)",
516 [
'integer',
'integer',
'integer',
'integer',
'text',
'integer'],
519 $feedback_option[
'question_fi'],
520 $feedback_option[
'answer'],
522 $feedback_option[
'feedback'],
523 $feedback_option[
'question']
531 $this->db->manipulateF(
532 "DELETE FROM qpl_a_mc WHERE question_fi = %s",
538 foreach ($this->answers as $key => $value) {
539 $answer_obj = $this->answers[$key];
540 $next_id = $this->db->nextId(
'qpl_a_mc');
541 $this->db->manipulateF(
542 "INSERT INTO qpl_a_mc (answer_id, question_fi, answertext, points, points_unchecked, aorder, imagefile, tstamp)
543 VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
544 [
'integer',
'integer',
'text',
'float',
'float',
'integer',
'text',
'integer'],
549 $answer_obj->getPoints(),
550 $answer_obj->getPointsUnchecked(),
551 $answer_obj->getOrder(),
552 $answer_obj->getImage(),
566 return "assMultipleChoice";
599 if (!empty($image_tempfilename)) {
600 $image_filename = str_replace(
" ",
"_", $image_filename);
601 $imagepath = $this->getImagePath();
602 if (!file_exists($imagepath)) {
609 if (!preg_match(
"/^image/", $mimetype)) {
610 unlink($imagepath . $image_filename);
614 if ($this->is_singleline && ($this->getThumbSize())) {
615 $this->generateThumbForFile(
617 $this->getImagePath(),
618 $this->getThumbSize()
629 $text = parent::getRTETextWithMediaObjects();
630 foreach ($this->answers as $index => $answer) {
631 $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->
getId(), 0, $index);
632 $answer_obj = $this->answers[$index];
633 $text .= $answer_obj->getAnswertext();
643 return $this->answers;
648 $this->answers = $answers;
656 foreach ($this->getAnswers() as $answer) {
668 $result[
'id'] = $this->
getId();
669 $result[
'type'] = (string) $this->getQuestionType();
670 $result[
'title'] = $this->getTitleForHTMLOutput();
671 $result[
'question'] = $this->formatSAQuestion($this->getQuestion());
672 $result[
'nr_of_tries'] = $this->getNrOfTries();
673 $result[
'shuffle'] = $this->getShuffle();
674 $result[
'selection_limit'] = (
int) $this->getSelectionLimit();
675 $result[
'feedback'] = [
676 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
677 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
682 foreach ($this->getAnswers() as $key => $answer_obj) {
683 if ((
string) $answer_obj->getImage()) {
686 array_push($answers, [
687 "answertext" => $this->formatSAQuestion($answer_obj->getAnswertext()),
688 "points_checked" => (
float) $answer_obj->getPointsChecked(),
689 "points_unchecked" => (
float) $answer_obj->getPointsUnchecked(),
690 "order" => (
int) $answer_obj->getOrder(),
691 "image" => (
string) $answer_obj->getImage(),
692 "feedback" => $this->formatSAQuestion(
693 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
697 $result[
'answers'] = $answers;
700 $result[
'path'] = $this->getImagePathWeb();
701 $result[
'thumb'] = $this->getThumbSize();
705 $result[
'mobs'] = $mobs;
707 return json_encode($result);
712 $answer = $this->answers[$index];
713 if (is_object($answer)) {
714 $this->deleteImage($answer->getImage());
715 $answer->setImage(
null);
721 return $this->current_user->getPref(
'tst_multiline_answers') ===
'1' ? 1 : 0;
726 $this->current_user->writePref(
'tst_multiline_answers', (
string) $setting);
740 $this->feedback_setting = $feedback_setting;
754 if ($this->feedback_setting) {
755 return $this->feedback_setting;
763 return 'feedback_correct_sc_mc';
768 $solutionSubmit = [];
769 $post = $this->dic->http()->wrapper()->post();
771 foreach ($this->getAnswers() as $index =>
$a) {
772 if (
$post->has(
"multiple_choice_result_$index")) {
773 $value =
$post->retrieve(
"multiple_choice_result_$index", $this->dic->refinery()->kindlyTo()->string());
774 if (is_numeric($value)) {
775 $solutionSubmit[] = $value;
779 return $solutionSubmit;
783 ?array $found_values,
786 if ($found_values === []
787 && $active_id !== 0) {
791 $found_values ??= [];
793 foreach ($this->answers as $key => $answer) {
794 if (in_array($key, $found_values)) {
795 $points += $answer->getPoints();
798 $points += $answer->getPointsUnchecked();
825 $maxStep = $this->lookupMaxStep($active_id, $pass);
827 $data = $this->db->queryF(
828 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
829 [
"integer",
"integer",
"integer",
"integer"],
830 [$active_id, $pass, $this->
getId(), $maxStep]
833 $data = $this->db->queryF(
834 "SELECT value1+1 as value1 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
835 [
"integer",
"integer",
"integer"],
836 [$active_id, $pass, $this->
getId()]
840 while ($row = $this->db->fetchAssoc(
$data)) {
841 $result->addKeyValue($row[
"value1"], $row[
"value1"]);
844 $points = $this->calculateReachedPoints($active_id, $pass);
845 $max_points = $this->getMaximumPoints();
847 $result->setReachedPercentage(($points / $max_points) * 100);
860 if ($index !==
null) {
861 return $this->getAnswer($index);
863 return $this->getAnswers();
869 $config = parent::buildTestPresentationConfig();
870 $config->setUseUnchangedAnswerLabel($this->
lng->txt(
'tst_mc_label_none_above'));
876 return $this->is_singleline;
882 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
883 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
884 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
885 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
887 'ass_mc_sel_lim_setting' => (
int) $this->getSelectionLimit(),
888 AdditionalInformationGenerator::KEY_FEEDBACK => [
889 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_INCOMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)),
890 AdditionalInformationGenerator::KEY_QUESTION_FEEDBACK_ON_COMPLETE => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true))
894 foreach ($this->getAnswers() as $key => $answer_obj) {
895 $result[AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTIONS][$key + 1] = [
896 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION => $this->formatSAQuestion($answer_obj->getAnswertext()),
897 AdditionalInformationGenerator::KEY_QUESTION_POINTS_CHECKED => (float) $answer_obj->getPointsChecked(),
898 AdditionalInformationGenerator::KEY_QUESTION_POINTS_UNCHECKED => (float) $answer_obj->getPointsUnchecked(),
899 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => (
int) $answer_obj->getOrder(),
900 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_IMAGE => (string) $answer_obj->getImage(),
901 AdditionalInformationGenerator::KEY_FEEDBACK => $this->formatSAQuestion(
902 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
912 array $solution_values
914 $solution_ids = array_map(
915 static fn(array $v): string => $v[
'value1'],
918 $parsed_solutions = [];
919 foreach ($this->getAnswers() as
$id => $answer) {
921 if (in_array(
$id, $solution_ids)) {
924 $parsed_solutions[$answer->getAnswertext()] = $additional_info
927 return $parsed_solutions;
932 $solution_ids = array_map(
933 static fn(array $v):
string => $v[
'value1'],
939 $checked =
'unchecked';
940 if (in_array($v->getId(), $solution_ids)) {
941 $checked =
'checked';
943 return "{$v->getAnswertext()} ({$this->lng->txt($checked)})";
953 .
"({$this->lng->txt('points')} "
954 .
"{$this->lng->txt('checked')}: {$v->getPointsChecked()}, "
955 .
"{$this->lng->txt('unchecked')}: {$v->getPointsUnchecked()})",
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
ASS_AnswerBinaryStateImage is a class for answers with a binary state indicator (checked/unchecked,...
getAnswertext()
Gets the answer text.
Class for multiple choice tests.
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
saveToDb(?int $original_id=null)
cloneQuestionTypeSpecificProperties(\assQuestion $target)
buildTestPresentationConfig()
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
addAnswer(string $answertext='', float $points=0.0, float $points_unchecked=0.0, int $order=0, ?string $answerimage=null, int $answer_id=-1)
Adds a possible answer for a multiple choice question.
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
setSelectionLimit(?int $selection_limit)
toJSON()
Returns a JSON representation of the question.
getSpecificFeedbackSetting()
Gets the current feedback settings in effect for the question.
getOperators(string $expression)
Get all available operations for a specific question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getAnswerCount()
Returns the number of answers.
getAnswerTableName()
Returns the name of the answer table in the database.
loadFromDb(int $question_id)
getCorrectSolutionForTextOutput(int $active_id, int $pass)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
setMultilineAnswerSetting($setting=0)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
getSpecificFeedbackAllCorrectOptionLabel()
removeAnswerImage($index)
isForcedEmptySolution(array $solutionSubmit)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="", private int $output_type=self::OUTPUT_ORDER)
assMultipleChoice constructor
calculateReachedPointsForSolution(?array $found_values, int $active_id=0)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
setAnswers(array $answers)
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
setSpecificFeedbackSetting(int $feedback_setting)
Sets the feedback settings in effect for the question.
getMultilineAnswerSetting()
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
flushAnswers()
Deletes all answers.
deleteAnswer($index=0)
Deletes an answer with a given index.
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.
getQuestionType()
Returns the question type of the question.
setImageFile($image_filename, $image_tempfilename="")
Sets the image file and uploads the image to the object's image directory.
getAnswer($index=0)
Returns an answer with a given index.
getExpressionTypes()
Get all available expression types for a specific question.
getRTETextWithMediaObjects()
& getAnswers()
Returns a reference to the answers array.
setIsSingleline(bool $is_singleline)
setOriginalId(?int $original_id)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
setShuffle(?bool $shuffle=true)
setQuestion(string $question="")
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
setAuthor(string $author="")
setThumbSize(int $a_size)
setComment(string $comment="")
setNrOfTries(int $a_nr_of_tries)
setLifecycle(ilAssQuestionLifecycle $lifecycle)
setTitle(string $title="")
saveQuestionDataToDb(?int $original_id=null)
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 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...
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...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const PercentageResultExpression
const EmptyAnswerExpression
const ExclusiveResultExpression
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
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
if(!file_exists('../ilias.ini.php'))