19declare(strict_types=1);
23use ILIAS\TestQuestionPool\ManipulateImagesInChoiceQuestionsTrait;
41 use ManipulateImagesInChoiceQuestionsTrait;
75 && $this->author !==
null && $this->author !==
''
76 && $this->question !==
null && $this->question !==
''
77 && $this->answers !== []
80 foreach ($this->answers as $answer) {
81 if ($answer->getAnswertext() ===
'' && $answer->getImage() ===
'') {
100 $result = $this->db->queryF(
105 if ($result->numRows() == 1) {
106 $data = $this->db->fetchAssoc($result);
107 $this->
setId($question_id);
122 $this->is_singleline =
$data[
'allow_images'] ===
null ||
$data[
'allow_images'] ===
'0';
123 $this->lastChange =
$data[
'tstamp'];
138 $result = $this->db->queryF(
139 "SELECT * FROM qpl_a_sc WHERE question_fi = %s ORDER BY aorder ASC",
144 if ($result->numRows() > 0) {
145 while (
$data = $this->db->fetchAssoc($result)) {
147 if (!file_exists($imagefilename)) {
148 $data[
"imagefile"] =
null;
157 $data[
"imagefile"] ?
$data[
"imagefile"] :
null,
160 $this->answers[] = $image;
164 parent::loadFromDb($question_id);
181 string $answertext =
'',
184 ?
string $answerimage =
null,
187 if (array_key_exists($order, $this->answers)) {
190 $this->getHtmlQuestionContentPurifier()->purify($answertext),
198 for ($i = 0; $i < $order; $i++) {
199 $newchoices[] = $this->answers[$i];
201 $newchoices[] = $answer;
202 for ($i = $order, $iMax = count($this->answers); $i < $iMax; $i++) {
203 $changed = $this->answers[$i];
204 $changed->setOrder($i + 1);
205 $newchoices[] = $changed;
207 $this->answers = $newchoices;
212 $this->getHtmlQuestionContentPurifier()->purify($answertext),
214 count($this->answers),
219 $this->answers[] = $answer;
231 return count($this->answers);
248 if (count($this->answers) < 1) {
251 if ($index >= count($this->answers)) {
255 return $this->answers[$index];
271 if (count($this->answers) < 1) {
274 if ($index >= count($this->answers)) {
277 $answer = $this->answers[$index];
278 if ($answer->hasImage()) {
279 $this->deleteImage($answer->getImage());
281 unset($this->answers[$index]);
282 $this->answers = array_values($this->answers);
283 for ($i = 0, $iMax = count($this->answers); $i < $iMax; $i++) {
284 if ($this->answers[$i]->
getOrder() > $index) {
285 $this->answers[$i]->setOrder($i);
310 foreach ($this->answers as $key => $value) {
311 if ($value->getPoints() > $points) {
312 $points = $value->getPoints();
321 bool $authorized_solution =
true
324 if (is_null($pass)) {
325 $pass = $this->getSolutionMaxPass($active_id);
327 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized_solution);
328 while (
$data = $this->db->fetchAssoc($result)) {
329 if (
$data[
'value1'] !==
'') {
330 array_push($found_values,
$data[
'value1']);
334 foreach ($this->answers as $key => $answer) {
335 if ($found_values !== []
336 && in_array($key, $found_values)) {
337 $points += $answer->getPoints();
347 $participant_solution = $preview_session->getParticipantsSolution();
350 foreach ($this->answers as $key => $answer) {
351 if (is_numeric($participant_solution)
352 && $key === (
int) $participant_solution) {
353 $points = $answer->getPoints();
357 return $this->ensureNonNegativePoints($points);
363 bool $authorized =
true
365 if (is_null($pass)) {
369 $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
370 function () use ($active_id, $pass, $authorized) {
371 $result = $this->getCurrentSolutionResultSet($active_id, $pass, $authorized);
374 if ($this->db->numRows($result)) {
375 $row = $this->db->fetchAssoc($result);
376 $update = $row[
"solution_id"];
379 $multiple_choice_result = $this->
http->wrapper()->post()->has(
'multiple_choice_result') ?
380 $this->
http->wrapper()->post()->retrieve(
'multiple_choice_result', $this->
refinery->kindlyTo()->string()) :
384 && $multiple_choice_result ===
'') {
385 $this->removeSolutionRecordById($update);
389 if ($update !== -1) {
390 $this->updateCurrentSolution($update, $multiple_choice_result,
null, $authorized);
394 if ($multiple_choice_result !==
'') {
395 $this->saveCurrentSolution($active_id, $pass, $multiple_choice_result,
null, $authorized);
405 $mc_result_key =
'multiple_choice_result' . $this->
getId() .
'ID';
407 $this->
http->wrapper()->post()->has($mc_result_key) &&
408 ($mc_result = $this->http->wrapper()->post()->retrieve($mc_result_key, $this->refinery->kindlyTo()->string())) !==
''
419 $this->db->manipulateF(
420 "DELETE FROM " . $this->getAdditionalTableName() .
" WHERE question_fi = %s",
425 $this->db->manipulateF(
426 "INSERT INTO " . $this->getAdditionalTableName(
427 ) .
" (question_fi, shuffle, allow_images, thumb_size) VALUES (%s, %s, %s, %s)",
428 [
"integer",
"text",
"text",
"integer" ],
432 $this->is_singleline ?
'0' :
'1',
433 $this->getThumbSize()
443 public function saveAnswerSpecificDataToDb(): void
445 if (!$this->is_singleline) {
449 $result = $this->db->queryF(
450 "SELECT * FROM qpl_fb_specific WHERE question_fi = %s",
454 $db_feedback = $this->db->fetchAll($result);
457 if (
sizeof($db_feedback) >= 1 && $this->getAdditionalContentEditingMode() ==
'default') {
459 $result = $this->db->queryF(
460 "SELECT answer_id, aorder FROM qpl_a_sc WHERE question_fi = %s",
464 $db_answers = $this->db->fetchAll($result);
467 $post_answer_order_for_id = [];
468 foreach ($this->answers as $answer) {
470 if ($answer->getId() !==
null && !in_array($answer->getId(), array_keys($post_answer_order_for_id))) {
472 if ($answer->getId() == -1) {
475 $post_answer_order_for_id[$answer->getId()] = $answer->getOrder();
481 if (
sizeof($post_answer_order_for_id) >= 1) {
482 $db_answer_order_for_id = [];
483 $db_answer_id_for_order = [];
484 foreach ($db_answers as $db_answer) {
485 $db_answer_order_for_id[intval($db_answer[
'answer_id'])] = intval($db_answer[
'aorder']);
486 $db_answer_id_for_order[intval($db_answer[
'aorder'])] = intval($db_answer[
'answer_id']);
492 $db_answer_ids = array_keys($db_answer_order_for_id);
493 $post_answer_ids = array_keys($post_answer_order_for_id);
494 $diff_db_post_answer_ids = array_diff($db_answer_ids, $post_answer_ids);
495 $unused_answer_ids = array_keys($diff_db_post_answer_ids);
498 $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($this->
getId(),
false);
500 foreach ($db_feedback as $feedback_option) {
502 if (in_array(intval($feedback_option[
'answer']), $unused_answer_ids)) {
507 $feedback_order_db = intval($feedback_option[
'answer']);
508 $db_answer_id = $db_answer_id_for_order[$feedback_order_db] ??
null;
512 if (is_null($db_answer_id) || $db_answer_id < 0) {
515 $feedback_order_post = $post_answer_order_for_id[$db_answer_id];
516 $feedback_option[
'answer'] = $feedback_order_post;
519 $next_id = $this->db->nextId(
'qpl_fb_specific');
520 $this->db->manipulateF(
521 "INSERT INTO qpl_fb_specific (feedback_id, question_fi, answer, tstamp, feedback, question)
522 VALUES (%s, %s, %s, %s, %s, %s)",
523 [
'integer',
'integer',
'integer',
'integer',
'text',
'integer'],
526 $feedback_option[
'question_fi'],
527 $feedback_option[
'answer'],
529 $feedback_option[
'feedback'],
530 $feedback_option[
'question']
538 $this->db->manipulateF(
539 "DELETE FROM qpl_a_sc WHERE question_fi = %s",
545 foreach ($this->answers as $key => $value) {
547 $answer_obj = $this->answers[$key];
548 $next_id = $this->db->nextId(
'qpl_a_sc');
549 $this->db->manipulateF(
550 "INSERT INTO qpl_a_sc (answer_id, question_fi, answertext, points, aorder, imagefile, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
551 [
'integer',
'integer',
'text',
'float',
'integer',
'text',
'integer'],
556 $answer_obj->getPoints(),
557 $answer_obj->getOrder(),
558 $answer_obj->getImage(),
573 return "assSingleChoice";
607 string $image_filename,
608 string $image_tempfilename =
''
610 if (empty($image_tempfilename)) {
614 $cleaned_image_filename = str_replace(
" ",
"_", $image_filename);
615 $imagepath = $this->getImagePath();
616 if (!file_exists($imagepath)) {
625 if (!preg_match(
"/^image/", $mimetype)) {
626 unlink($imagepath . $cleaned_image_filename);
630 if ($this->is_singleline && $this->getThumbSize()) {
631 $this->generateThumbForFile(
632 $cleaned_image_filename,
633 $this->getImagePath(),
634 $this->getThumbSize()
647 $text = parent::getRTETextWithMediaObjects();
648 foreach (array_keys($this->answers) as $index) {
649 $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->
getId(), 0, $index);
650 $answer_obj = $this->answers[$index];
651 $text .= $answer_obj->getAnswertext();
661 return $this->answers;
666 $this->answers = $answers;
672 foreach ($this->getAnswers() as $answer) {
684 $result[
'id'] = $this->
getId();
685 $result[
'type'] = (string) $this->getQuestionType();
686 $result[
'title'] = $this->getTitleForHTMLOutput();
687 $result[
'question'] = $this->formatSAQuestion($this->getQuestion());
688 $result[
'nr_of_tries'] = $this->getNrOfTries();
689 $result[
'shuffle'] = $this->getShuffle();
691 $result[
'feedback'] = [
692 'onenotcorrect' => $this->formatSAQuestion(
693 $this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
false)
695 'allcorrect' => $this->formatSAQuestion(
696 $this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(),
true)
702 foreach ($this->getAnswers() as $key => $answer_obj) {
703 if ((
string) $answer_obj->getImage()) {
706 array_push($answers, [
707 "answertext" => $this->formatSAQuestion($answer_obj->getAnswertext()),
708 'html_id' => $this->getId() .
'_' . $key,
709 "points" => (
float) $answer_obj->getPoints(),
710 "order" => (
int) $answer_obj->getOrder(),
711 "image" => (
string) $answer_obj->getImage(),
712 "feedback" => $this->formatSAQuestion(
713 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation(
721 $result[
'answers'] = $answers;
723 $result[
'path'] = $this->getImagePathWeb();
724 $result[
'thumb'] = $this->getThumbSize();
728 $result[
'mobs'] = $mobs;
730 return json_encode($result);
735 $answer = $this->answers[$index];
736 if (is_object($answer)) {
737 $this->deleteImage($answer->getImage());
738 $answer->setImage(
null);
744 return $this->current_user->getPref(
'tst_multiline_answers') ===
'1' ? 1 : 0;
749 $this->current_user->writePref(
'tst_multiline_answers', (
string) $setting);
761 $this->feedback_setting = $feedback_setting;
766 return $this->feedback_setting;
771 return 'feedback_correct_sc_mc';
794 $maxStep = $this->lookupMaxStep($active_id, $pass);
796 $data = $this->db->queryF(
797 "SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
798 [
"integer",
"integer",
"integer",
"integer"],
799 [$active_id, $pass, $this->
getId(), $maxStep]
802 $data = $this->db->queryF(
803 "SELECT * FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
804 [
"integer",
"integer",
"integer"],
805 [$active_id, $pass, $this->
getId()]
809 $row = $this->db->fetchAssoc(
$data);
813 $result->addKeyValue($row[
"value1"], $row[
"value1"]);
816 $points = $this->calculateReachedPoints($active_id, $pass);
817 $max_points = $this->getMaximumPoints();
819 $result->setReachedPercentage(($points / $max_points) * 100);
832 if ($index !==
null) {
833 return $this->getAnswer($index);
835 return $this->getAnswers();
843 int $original_question_id,
844 int $clone_question_id,
845 int $original_parent_id,
848 parent::afterSyncWithOriginal($original_question_id, $clone_question_id, $original_parent_id, $clone_parent_id);
850 $original_image_path = $this->question_files->buildImagePath($original_question_id, $original_parent_id);
851 $clone_image_path = $this->question_files->buildImagePath($clone_question_id, $clone_parent_id);
854 if (is_dir($clone_image_path)) {
862 return $this->is_singleline;
867 $this->is_singleline = $is_singleline;
872 return $this->feedback_setting;
877 $this->feedback_setting = $feedback_setting;
883 AdditionalInformationGenerator::KEY_QUESTION_TYPE => (string) $this->getQuestionType(),
884 AdditionalInformationGenerator::KEY_QUESTION_TITLE => $this->getTitleForHTMLOutput(),
885 AdditionalInformationGenerator::KEY_QUESTION_TEXT => $this->formatSAQuestion($this->getQuestion()),
886 AdditionalInformationGenerator::KEY_QUESTION_SHUFFLE_ANSWER_OPTIONS => $additional_info
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_REACHABLE_POINTS => (float) $answer_obj->getPoints(),
898 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_ORDER => (
int) $answer_obj->getOrder(),
899 AdditionalInformationGenerator::KEY_QUESTION_ANSWER_OPTION_IMAGE => (string) $answer_obj->getImage(),
900 AdditionalInformationGenerator::KEY_FEEDBACK => $this->formatSAQuestion(
901 $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), 0, $key)
911 array $solution_values
913 if ($solution_values === []
914 || !array_key_exists(0, $solution_values)
915 || !is_array($solution_values[0])) {
919 return $this->getAnswer((
int) $solution_values[0][
'value1'])->getAnswertext();
924 if ($solution_values === []
925 || !array_key_exists(0, $solution_values)
926 || !is_array($solution_values[0])) {
930 return $this->getAnswer((
int) $solution_values[0][
'value1'])->getAnswertext();
939 $c[] = $v->getAnswertext()
940 .
"({$this->lng->txt('points')}: {$v->getPoints()})";
Class for answers with a binary state indicator.
getPoints()
Gets the points.
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)
Class for single choice questions.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
const FEEDBACK_MODE_ALL_ANSWERS
setSpecificFeedbackSetting(int $feedback_setting)
Sets the feedback settings in effect for the question.
getQuestionType()
Returns the question type of the question.
getAnswerCount()
Returns the number of answers.
setMultilineAnswerSetting($setting='0')
toJSON()
Returns a JSON representation of the question.
setFeedbackSetting(int $feedback_setting)
getRTETextWithMediaObjects()
afterSyncWithOriginal(int $original_question_id, int $clone_question_id, int $original_parent_id, int $clone_parent_id)
{}
addAnswer(string $answertext='', float $points=0.0, int $order=0, ?string $answerimage=null, int $answer_id=-1)
setImageFile(string $image_filename, string $image_tempfilename='')
Sets the image file and uploads the image to the object's image directory.
const FEEDBACK_MODE_SELECTED_ANSWERS
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
getAnswer(int $index=0)
Returns an answer with a given index.
getCorrectSolutionForTextOutput(int $active_id, int $pass)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="", int $output_type=self::OUTPUT_ORDER)
getSpecificFeedbackAllCorrectOptionLabel()
removeAnswerImage(int $index)
const FEEDBACK_MODE_CORRECT_ANSWERS
calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $preview_session)
deleteAnswer(int $index=0)
Deletes an answer with a given index.
getSpecificFeedbackSetting()
getExpressionTypes()
Get all available expression types for a specific question.
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
cloneQuestionTypeSpecificProperties(\assQuestion $target)
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.
loadFromDb(int $question_id)
saveToDb(?int $original_id=null)
savePreviewData(ilAssQuestionPreviewSession $previewSession)
getAnswers()
Returns a reference to the answers array.
solutionValuesToText(array $solution_values)
MUST convert the given solution values into text.
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
getOperators(string $expression)
Get all available operations for a specific question.
flushAnswers()
Deletes all answers.
getMultilineAnswerSetting()
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
setAnswers(array $answers)
setIsSingleline(bool $is_singleline)
toLog(AdditionalInformationGenerator $additional_info)
MUST return an array of the question settings that can be stored in the log.
getAdditionalTableName()
Returns the name of the additional question data table in the database.
getAnswerTableName()
Returns the name of the answer table in the database.
static getDraftInstance()
static getInstance($identifier)
setParticipantsSolution($participantSolution)
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 rCopy(string $a_sdir, string $a_tdir, bool $preserveTimeAttributes=false)
Copies content of a directory $a_sdir recursively to a directory $a_tdir.
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...
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 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...
saveAnswerSpecificDataToDb()
Saves the answer specific records into a question types answer table.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static http()
Fetches the global http state from ILIAS.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
if(!file_exists('../ilias.ini.php'))