ILIAS  trunk Revision v11.0_alpha-1723-g8e69f309bab
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.assMultipleChoice.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
26 
43 {
45 
46  public const OUTPUT_ORDER = 0;
47  public const OUTPUT_RANDOM = 1;
48 
49  public array $answers = [];
50  public bool $is_singleline = true;
51  public int $feedback_setting = 0;
52  protected ?int $selection_limit = null;
53 
54  public function setIsSingleline(bool $is_singleline): void
55  {
56  $this->is_singleline = $is_singleline;
57  }
58 
73  public function __construct(
74  string $title = "",
75  string $comment = "",
76  string $author = "",
77  int $owner = -1,
78  string $question = "",
79  private int $output_type = self::OUTPUT_ORDER
80  ) {
82  $this->answers = [];
83  $this->shuffle = true;
84  }
85 
86  public function getSelectionLimit(): ?int
87  {
89  }
90 
91  public function setSelectionLimit(?int $selection_limit): void
92  {
93  $this->selection_limit = $selection_limit;
94  }
95 
96  public function isComplete(): bool
97  {
98  return $this->title !== ''
99  && $this->author !== ''
100  && $this->question !== ''
101  && $this->getAnswerCount() > 0
102  && $this->getMaximumPoints() >= 0;
103  }
104 
105  public function saveToDb(?int $original_id = null): void
106  {
110  parent::saveToDb($original_id);
111  }
112 
113  public function loadFromDb(int $question_id): void
114  {
115  $result = $this->db->queryF(
116  "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",
117  ["integer"],
118  [$question_id]
119  );
120  if ($result->numRows() == 1) {
121  $data = $this->db->fetchAssoc($result);
122  $this->setId($question_id);
123  $this->setObjId($data["obj_fi"]);
124  $this->setTitle($data["title"] ?? '');
125  $this->setNrOfTries($data['nr_of_tries']);
126  $this->setComment($data["description"] ?? '');
127  $this->setOriginalId($data["original_id"]);
128  $this->setAuthor($data["author"]);
129  $this->setPoints($data["points"]);
130  $this->setOwner($data["owner"]);
131  $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"] ?? '', 1));
132  $shuffle = (is_null($data['shuffle'])) ? true : $data['shuffle'];
133  $this->setShuffle((bool) $shuffle);
134  if ($data['thumb_size'] !== null && $data['thumb_size'] >= $this->getMinimumThumbSize()) {
135  $this->setThumbSize($data['thumb_size']);
136  }
137  $this->is_singleline = $data['allow_images'] === null || $data['allow_images'] === '0';
138  $this->lastChange = $data['tstamp'];
139  $this->setSelectionLimit((int) $data['selection_limit'] > 0 ? (int) $data['selection_limit'] : null);
140  if (isset($data['feedback_setting'])) {
141  $this->feedback_setting = $data['feedback_setting'];
142  }
143 
144  try {
148  }
149 
150  try {
151  $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
152  } catch (ilTestQuestionPoolException $e) {
153  }
154  }
155 
156  $result = $this->db->queryF(
157  "SELECT * FROM qpl_a_mc WHERE question_fi = %s ORDER BY aorder ASC",
158  ['integer'],
159  [$question_id]
160  );
161  if ($result->numRows() > 0) {
162  while ($data = $this->db->fetchAssoc($result)) {
163  $imagefilename = $this->getImagePath() . $data["imagefile"];
164  if (!file_exists($imagefilename)) {
165  $data["imagefile"] = null;
166  }
167  $data["answertext"] = ilRTE::_replaceMediaObjectImageSrc($data["answertext"] ?? '', 1);
168 
169  $answer = new ASS_AnswerMultipleResponseImage(
170  $data["answertext"],
171  $data["points"],
172  $data["aorder"],
173  $data["answer_id"]
174  );
175  $answer->setPointsUnchecked($data["points_unchecked"]);
176  $answer->setImage($data["imagefile"] ? $data["imagefile"] : null);
177  array_push($this->answers, $answer);
178  }
179  }
180 
181  parent::loadFromDb($question_id);
182  }
183 
185  \assQuestion $target
186  ): \assQuestion {
187  $this->cloneImages(
188  $this->getId(),
189  $this->getObjId(),
190  $target->getId(),
191  $target->getObjId(),
192  $this->getAnswers()
193  );
194  return $target;
195  }
196 
211  public function addAnswer(
212  string $answertext = '',
213  float $points = 0.0,
214  float $points_unchecked = 0.0,
215  int $order = 0,
216  ?string $answerimage = null,
217  int $answer_id = -1
218  ): void {
219  if (array_key_exists($order, $this->answers)) {
220  // insert answer
221  $answer = new ASS_AnswerMultipleResponseImage(
222  $this->getHtmlQuestionContentPurifier()->purify($answertext),
223  $points,
224  $order,
225  -1,
226  0
227  );
228  $answer->setPointsUnchecked($points_unchecked);
229  $answer->setImage($answerimage);
230  $newchoices = [];
231  for ($i = 0; $i < $order; $i++) {
232  $newchoices[] = $this->answers[$i];
233  }
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;
239  }
240  $this->answers = $newchoices;
241  return;
242  } else {
243  $answer = new ASS_AnswerMultipleResponseImage(
244  $this->getHtmlQuestionContentPurifier()->purify($answertext),
245  $points,
246  count($this->answers),
247  $answer_id,
248  0
249  );
250  $answer->setPointsUnchecked($points_unchecked);
251  $answer->setImage($answerimage);
252  $this->answers[] = $answer;
253  }
254  }
255 
262  public function getAnswerCount(): int
263  {
264  return count($this->answers);
265  }
266 
275  public function getAnswer($index = 0): ?object
276  {
277  if ($index < 0) {
278  return null;
279  }
280  if (count($this->answers) < 1) {
281  return null;
282  }
283  if ($index >= count($this->answers)) {
284  return null;
285  }
286 
287  return $this->answers[$index];
288  }
289 
297  public function deleteAnswer($index = 0): void
298  {
299  if ($index < 0) {
300  return;
301  }
302  if (count($this->answers) < 1) {
303  return;
304  }
305  if ($index >= count($this->answers)) {
306  return;
307  }
308  $answer = $this->answers[$index];
309  if ($answer->hasImage()) {
310  $this->deleteImage($answer->getImage());
311  }
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);
317  }
318  }
319  }
320 
326  public function flushAnswers(): void
327  {
328  $this->answers = [];
329  }
330 
336  public function getMaximumPoints(): float
337  {
338  $total_max_points = 0.0;
339  foreach ($this->getAnswers() as $answer) {
340  $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked());
341  }
342  return $total_max_points;
343  }
344 
345  public function calculateReachedPoints(
346  int $active_id,
347  ?int $pass = null,
348  bool $authorized_solution = true
349  ): float {
350  $found_values = [];
351  if ($pass === null) {
352  $pass = $this->getSolutionMaxPass($active_id);
353  }
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']);
358  }
359  }
360 
361  return $this->calculateReachedPointsForSolution($found_values, $active_id);
362  }
363 
364  public function validateSolutionSubmit(): bool
365  {
366  $submit = $this->getSolutionSubmit();
367 
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()
374  );
375 
376  $this->tpl->setOnScreenMessage('failure', $failureMsg, true);
377  return false;
378  }
379  }
380 
381  return true;
382  }
383 
384  protected function isForcedEmptySolution(array $solutionSubmit): bool
385  {
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);
388  }
389 
390  public function saveWorkingData(
391  int $active_id,
392  ?int $pass = null,
393  bool $authorized = true
394  ): bool {
395  $pass = $pass ?? ilObjTest::_getPass($active_id);
396 
397  $answer = $this->getSolutionSubmit();
398  $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(
399  function () use ($answer, $active_id, $pass, $authorized) {
400  $this->removeCurrentSolution($active_id, $pass, $authorized);
401 
402  foreach ($answer as $value) {
403  if ($value !== '') {
404  $this->saveCurrentSolution($active_id, $pass, $value, null, $authorized);
405  }
406  }
407 
408  // fau: testNav - write a dummy entry for the evil mc questions with "None of the above" checked
409  if ($this->isForcedEmptySolution($answer)) {
410  $this->saveCurrentSolution($active_id, $pass, 'mc_none_above', null, $authorized);
411  }
412  // fau.
413  }
414  );
415 
416  return true;
417  }
418 
420  {
421  if (!$this->is_singleline) {
423  }
424 
425  // save additional data
426  $this->db->replace(
427  $this->getAdditionalTableName(),
428  [
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()]
434  ],
435  ['question_fi' => ['integer', $this->getId()]]
436  );
437  }
438 
439  public function saveAnswerSpecificDataToDb(): void
440  {
441  // Get all feedback entries
442  $result = $this->db->queryF(
443  "SELECT * FROM qpl_fb_specific WHERE question_fi = %s",
444  ['integer'],
445  [$this->getId()]
446  );
447  $db_feedback = $this->db->fetchAll($result);
448 
449  // Check if feedback exists and the regular editor is used and not the page editor
450  if (sizeof($db_feedback) >= 1 && $this->getAdditionalContentEditingMode() == 'default') {
451  // Get all existing answer data for question
452  $result = $this->db->queryF(
453  "SELECT answer_id, aorder FROM qpl_a_mc WHERE question_fi = %s",
454  ['integer'],
455  [$this->getId()]
456  );
457  $db_answers = $this->db->fetchAll($result);
458 
459  // Collect old and new order entries by ids and order to calculate a diff/intersection and remove/update feedback
460  $post_answer_order_for_id = [];
461  foreach ($this->answers as $answer) {
462  // Only the first appearance of an id is used
463  if ($answer->getId() !== null && !in_array($answer->getId(), array_keys($post_answer_order_for_id))) {
464  // -1 is happening while import and also if a new multi line answer is generated
465  if ($answer->getId() == -1) {
466  continue;
467  }
468  $post_answer_order_for_id[$answer->getId()] = $answer->getOrder();
469  }
470  }
471 
472  // If there is no usable ids from post, it's better to not touch the feedback
473  // This is useful since the import is also using this function or the first creation of a new question in general
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']);
480  }
481 
482  // Handle feedback
483  // the diff between the already existing answer ids from the Database and the answer ids from post
484  // feedback related to the answer ids should be deleted or in our case not recreated.
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);
489 
490  // Delete all feedback in the database
491  $this->feedbackOBJ->deleteSpecificAnswerFeedbacks($this->getId(), false);
492  // Recreate feedback
493  foreach ($db_feedback as $feedback_option) {
494  // skip feedback which answer is deleted
495  if (in_array(intval($feedback_option['answer']), $unused_answer_ids)) {
496  continue;
497  }
498 
499  // Reorder feedback
500  $feedback_order_db = intval($feedback_option['answer']);
501  $db_answer_id = $db_answer_id_for_order[$feedback_order_db];
502  // This cuts feedback that currently would have no corresponding answer
503  // This case can happen while copying "broken" questions
504  // Or when saving a question with less answers than feedback
505  if (is_null($db_answer_id) || $db_answer_id < 0) {
506  continue;
507  }
508  $feedback_order_post = $post_answer_order_for_id[$db_answer_id];
509  $feedback_option['answer'] = $feedback_order_post;
510 
511  // Recreate remaining feedback in database
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'],
517  [
518  $next_id,
519  $feedback_option['question_fi'],
520  $feedback_option['answer'],
521  time(),
522  $feedback_option['feedback'],
523  $feedback_option['question']
524  ]
525  );
526  }
527  }
528  }
529 
530  // Delete all entries in qpl_a_mc for question
531  $this->db->manipulateF(
532  "DELETE FROM qpl_a_mc WHERE question_fi = %s",
533  ['integer'],
534  [$this->getId()]
535  );
536 
537  // Recreate answers one by one
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'],
545  [
546  $next_id,
547  $this->getId(),
548  ilRTE::_replaceMediaObjectImageSrc($answer_obj->getAnswertext(), 0),
549  $answer_obj->getPoints(),
550  $answer_obj->getPointsUnchecked(),
551  $answer_obj->getOrder(),
552  $answer_obj->getImage(),
553  time()
554  ]
555  );
556  }
557  }
558 
564  public function getQuestionType(): string
565  {
566  return "assMultipleChoice";
567  }
568 
574  public function getAdditionalTableName(): string
575  {
576  return "qpl_qst_mc";
577  }
578 
584  public function getAnswerTableName(): string
585  {
586  return "qpl_a_mc";
587  }
588 
596  public function setImageFile($image_filename, $image_tempfilename = ""): int
597  {
598  $result = 0;
599  if (!empty($image_tempfilename)) {
600  $image_filename = str_replace(" ", "_", $image_filename);
601  $imagepath = $this->getImagePath();
602  if (!file_exists($imagepath)) {
603  ilFileUtils::makeDirParents($imagepath);
604  }
605  if (!ilFileUtils::moveUploadedFile($image_tempfilename, $image_filename, $imagepath . $image_filename)) {
606  $result = 2;
607  } else {
608  $mimetype = ilObjMediaObject::getMimeType($imagepath . $image_filename);
609  if (!preg_match("/^image/", $mimetype)) {
610  unlink($imagepath . $image_filename);
611  $result = 1;
612  } else {
613  // create thumbnail file
614  if ($this->is_singleline && ($this->getThumbSize())) {
615  $this->generateThumbForFile(
616  $image_filename,
617  $this->getImagePath(),
618  $this->getThumbSize()
619  );
620  }
621  }
622  }
623  }
624  return $result;
625  }
626 
627  public function getRTETextWithMediaObjects(): string
628  {
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();
634  }
635  return $text;
636  }
637 
641  public function &getAnswers(): array
642  {
643  return $this->answers;
644  }
645 
646  public function setAnswers(array $answers): void
647  {
648  $this->answers = $answers;
649  }
650 
655  {
656  foreach ($this->getAnswers() as $answer) {
657  /* @var ASS_AnswerBinaryStateImage $answer */
658  $answer->setAnswertext($migrator->migrateToLmContent($answer->getAnswertext()));
659  }
660  }
661 
665  public function toJSON(): string
666  {
667  $result = [];
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))
678  ];
679 
680  $answers = [];
681  $has_image = false;
682  foreach ($this->getAnswers() as $key => $answer_obj) {
683  if ((string) $answer_obj->getImage()) {
684  $has_image = true;
685  }
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)
694  )
695  ]);
696  }
697  $result['answers'] = $answers;
698 
699  if ($has_image) {
700  $result['path'] = $this->getImagePathWeb();
701  $result['thumb'] = $this->getThumbSize();
702  }
703 
704  $mobs = ilObjMediaObject::_getMobsOfObject("qpl:html", $this->getId());
705  $result['mobs'] = $mobs;
706 
707  return json_encode($result);
708  }
709 
710  public function removeAnswerImage($index): void
711  {
712  $answer = $this->answers[$index];
713  if (is_object($answer)) {
714  $this->deleteImage($answer->getImage());
715  $answer->setImage(null);
716  }
717  }
718 
719  public function getMultilineAnswerSetting(): int
720  {
721  return $this->current_user->getPref('tst_multiline_answers') === '1' ? 1 : 0;
722  }
723 
724  public function setMultilineAnswerSetting($setting = 0): void
725  {
726  $this->current_user->writePref('tst_multiline_answers', (string) $setting);
727  }
728 
738  public function setSpecificFeedbackSetting(int $feedback_setting): void
739  {
740  $this->feedback_setting = $feedback_setting;
741  }
742 
752  public function getSpecificFeedbackSetting(): int
753  {
754  if ($this->feedback_setting) {
756  } else {
757  return 1;
758  }
759  }
760 
762  {
763  return 'feedback_correct_sc_mc';
764  }
765 
766  protected function getSolutionSubmit(): array
767  {
768  $solutionSubmit = [];
769  $post = $this->dic->http()->wrapper()->post();
770 
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;
776  }
777  }
778  }
779  return $solutionSubmit;
780  }
781 
783  ?array $found_values,
784  int $active_id = 0
785  ): float {
786  if ($found_values === []
787  && $active_id !== 0) {
788  return 0.0;
789  }
790 
791  $found_values ??= [];
792  $points = 0.0;
793  foreach ($this->answers as $key => $answer) {
794  if (in_array($key, $found_values)) {
795  $points += $answer->getPoints();
796  continue;
797  }
798  $points += $answer->getPointsUnchecked();
799  }
800 
801  return $points;
802  }
803 
804  public function getOperators(string $expression): array
805  {
806  return ilOperatorsExpressionMapping::getOperatorsByExpression($expression);
807  }
808 
809  public function getExpressionTypes(): array
810  {
811  return [
816  ];
817  }
818 
819  public function getUserQuestionResult(
820  int $active_id,
821  int $pass
823  $result = new ilUserQuestionResult($this, $active_id, $pass);
824 
825  $maxStep = $this->lookupMaxStep($active_id, $pass);
826  if ($maxStep > 0) {
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]
831  );
832  } else {
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()]
837  );
838  }
839 
840  while ($row = $this->db->fetchAssoc($data)) {
841  $result->addKeyValue($row["value1"], $row["value1"]);
842  }
843 
844  $points = $this->calculateReachedPoints($active_id, $pass);
845  $max_points = $this->getMaximumPoints();
846 
847  $result->setReachedPercentage(($points / $max_points) * 100);
848 
849  return $result;
850  }
851 
858  public function getAvailableAnswerOptions($index = null)
859  {
860  if ($index !== null) {
861  return $this->getAnswer($index);
862  } else {
863  return $this->getAnswers();
864  }
865  }
866 
868  {
869  $config = parent::buildTestPresentationConfig();
870  $config->setUseUnchangedAnswerLabel($this->lng->txt('tst_mc_label_none_above'));
871  return $config;
872  }
873 
874  public function isSingleline(): bool
875  {
876  return $this->is_singleline;
877  }
878 
879  public function toLog(AdditionalInformationGenerator $additional_info): array
880  {
881  $result = [
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))
891  ]
892  ];
893 
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)
903  )
904  ];
905  }
906 
907  return $result;
908  }
909 
910  protected function solutionValuesToLog(
911  AdditionalInformationGenerator $additional_info,
912  array $solution_values
913  ): array {
914  $solution_ids = array_map(
915  static fn(array $v): string => $v['value1'],
916  $solution_values
917  );
918  $parsed_solutions = [];
919  foreach ($this->getAnswers() as $id => $answer) {
920  $checked = false;
921  if (in_array($id, $solution_ids)) {
922  $checked = true;
923  }
924  $parsed_solutions[$answer->getAnswertext()] = $additional_info
925  ->getCheckedUncheckedTagForBool($checked);
926  }
927  return $parsed_solutions;
928  }
929 
930  public function solutionValuesToText(array $solution_values): array
931  {
932  $solution_ids = array_map(
933  static fn(array $v): string => $v['value1'],
934  $solution_values
935  );
936 
937  return array_map(
938  function (ASS_AnswerMultipleResponseImage $v) use ($solution_ids): string {
939  $checked = 'unchecked';
940  if (in_array($v->getId(), $solution_ids)) {
941  $checked = 'checked';
942  }
943  return "{$v->getAnswertext()} ({$this->lng->txt($checked)})";
944  },
945  $this->getAnswers()
946  );
947  }
948 
949  public function getCorrectSolutionForTextOutput(int $active_id, int $pass): array
950  {
951  return array_map(
952  fn(ASS_AnswerMultipleResponseImage $v): string => $v->getAnswertext()
953  . "({$this->lng->txt('points')} "
954  . "{$this->lng->txt('checked')}: {$v->getPointsChecked()}, "
955  . "{$this->lng->txt('unchecked')}: {$v->getPointsUnchecked()})",
956  $this->getAnswers()
957  );
958  }
959 }
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...
flushAnswers()
Deletes all answers.
setNrOfTries(int $a_nr_of_tries)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
toJSON()
Returns a JSON representation of the question.
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.
toLog(AdditionalInformationGenerator $additional_info)
__construct(string $title="", string $comment="", string $author="", int $owner=-1, string $question="", private int $output_type=self::OUTPUT_ORDER)
assMultipleChoice constructor
solutionValuesToText(array $solution_values)
saveWorkingData(int $active_id, ?int $pass=null, bool $authorized=true)
setOwner(int $owner=-1)
getCorrectSolutionForTextOutput(int $active_id, int $pass)
ASS_AnswerBinaryStateImage is a class for answers with a binary state indicator (checked/unchecked, set/unset) and an image file.
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...
getImagePathWeb()
Returns the web image path for web accessable images of a question.
setThumbSize(int $a_size)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getQuestionType()
Returns the question type of the question.
cloneQuestionTypeSpecificProperties(\assQuestion $target)
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.
getUserQuestionResult(int $active_id, int $pass)
Get the user solution for a question by active_id and the test pass.
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
setComment(string $comment="")
Class for multiple choice tests.
calculateReachedPointsForSolution(?array $found_values, int $active_id=0)
lmMigrateQuestionTypeSpecificContent(ilAssSelfAssessmentMigrator $migrator)
getId()
Gets the answer id.
setSelectionLimit(?int $selection_limit)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
deleteAnswer($index=0)
Deletes an answer with a given index.
saveCurrentSolution(int $active_id, int $pass, $value1, $value2, bool $authorized=true, $tstamp=0)
getImagePath($question_id=null, $object_id=null)
Returns the image path for web accessable images of a question.
saveAdditionalQuestionDataToDb()
Saves a record to the question types additional data table.
static getMimeType(string $a_file, bool $a_external=false)
get mime type for file
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getAnswerCount()
Returns the number of answers.
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
getSpecificFeedbackSetting()
Gets the current feedback settings in effect for the question.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
solutionValuesToLog(AdditionalInformationGenerator $additional_info, array $solution_values)
getAvailableAnswerOptions($index=null)
If index is null, the function returns an array with all anwser options Else it returns the specific ...
getOperators(string $expression)
Get all available operations for a specific question.
loadFromDb(int $question_id)
static moveUploadedFile(string $a_file, string $a_name, string $a_target, bool $a_raise_errors=true, string $a_mode="move_uploaded")
move uploaded file
getAnswer($index=0)
Returns an answer with a given index.
setPoints(float $points)
setObjId(int $obj_id=0)
& getAnswers()
Returns a reference to the answers array.
saveQuestionDataToDb(?int $original_id=null)
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
getExpressionTypes()
Get all available expression types for a specific question.
saveToDb(?int $original_id=null)
setSpecificFeedbackSetting(int $feedback_setting)
Sets the feedback settings in effect for the question.
getMaximumPoints()
Returns the maximum points, a learner can reach answering the question.
getAnswertext()
Gets the answer text.
setImageFile($image_filename, $image_tempfilename="")
Sets the image file and uploads the image to the object&#39;s image directory.
getSolutionMaxPass(int $active_id)
removeCurrentSolution(int $active_id, int $pass, bool $authorized=true)
setIsSingleline(bool $is_singleline)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setId(int $id=-1)
__construct(Container $dic, ilPlugin $plugin)
getAdditionalTableName()
Returns the name of the additional question data table in the database.
setOriginalId(?int $original_id)
setTitle(string $title="")
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
setLifecycle(ilAssQuestionLifecycle $lifecycle)
getAnswerTableName()
Returns the name of the answer table in the database.
getCurrentSolutionResultSet(int $active_id, int $pass, bool $authorized=true)
lookupMaxStep(int $active_id, int $pass)
setAuthor(string $author="")
$post
Definition: ltitoken.php:46
setShuffle(?bool $shuffle=true)
setAdditionalContentEditingMode(?string $additionalContentEditingMode)
calculateReachedPoints(int $active_id, ?int $pass=null, bool $authorized_solution=true)
isForcedEmptySolution(array $solutionSubmit)
setQuestion(string $question="")