ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.assMultipleChoiceGUI.php
Go to the documentation of this file.
1 <?php
2 
19 require_once './Modules/Test/classes/inc.AssessmentConstants.php';
20 
36 {
37  private bool $rebuild_thumbnails = false;
38 
47  public function __construct($id = -1)
48  {
50  $this->object = new assMultipleChoice();
51  if ($id >= 0) {
52  $this->object->loadFromDb($id);
53  }
54  }
55 
59  public function hasInlineFeedback(): bool
60  {
61  return $this->object->feedbackOBJ->isSpecificAnswerFeedbackAvailable($this->object->getId());
62  }
63 
67  protected function writePostData(bool $always = false): int
68  {
69  $hasErrors = (!$always) ? $this->editQuestion(true) : false;
70  if (!$hasErrors) {
71  $is_singleline = $this->getEditAnswersSingleLine();
72  $form = $this->buildEditForm($is_singleline);
73  $form->setValuesByPost();
75  $this->writeQuestionSpecificPostData($form);
76  $this->writeAnswerSpecificPostData($form);
77  $this->saveTaxonomyAssignments();
78  return 0;
79  }
80  return 1;
81  }
82 
90  protected function getEditAnswersSingleLine($checkonly = false): bool
91  {
92  if ($this->object->getSelfAssessmentEditingMode()) {
93  return $this->object->isSingleline();
94  }
95 
96  if ($checkonly) {
97  $types = $_POST['types'] ?? '0';
98  return $types === '0' ? true : false;
99  }
100 
101  $lastChange = $this->object->getLastChange();
102  if (empty($lastChange) && !isset($_POST['types'])) {
103  // a new question is edited
104  return $this->object->getMultilineAnswerSetting() ? false : true;
105  } else {
106  // a saved question is edited
107  return $this->object->isSingleline();
108  }
109  }
110 
118  public function editQuestion($checkonly = false): bool
119  {
120  $save = $this->isSaveCommand();
121  $this->getQuestionTemplate();
122 
123  $is_singleline = $this->getEditAnswersSingleLine($checkonly);
124 
125  $form = $this->buildEditForm($is_singleline);
126 
127  if ($is_singleline) {
128  $form->setMultipart(true);
129  } else {
130  $form->setMultipart(false);
131  }
132 
133  $errors = false;
134 
135  if ($save) {
136  $form->getItemByPostVar('selection_limit')->setMaxValue(count((array) $_POST['choice']['answer']));
137 
138  $form->setValuesByPost();
139  $errors = !$this->checkMaxPointsNotNegative($form) || !$form->checkInput();
140  if ($errors) {
141  $checkonly = false;
142  }
143  }
144 
145  if (!$checkonly) {
146  $this->tpl->setVariable("QUESTION_DATA", $form->getHTML());
147  }
148  return $errors;
149  }
150 
151  private function checkMaxPointsNotNegative(ilPropertyFormGUI $form): bool
152  {
153  $choice = $form->getItemByPostVar('choice');
154  if (!$choice instanceof ilMultipleChoiceWizardInputGUI) {
155  return true;
156  }
157 
158  $answers = $choice->getValues();
159  $total_max_points = 0;
161  foreach ($answers as $answer) {
162  $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked());
163  }
164 
165  if ($total_max_points < 0) {
166  $choice->setAlert($this->lng->txt('total_max_points_cannot_be_negative'));
167  return false;
168  }
169 
170  return true;
171  }
172 
174  {
175  parent::addBasicQuestionFormProperties($form);
176  $form->getItemByPostVar('question')->setInitialRteWidth('100');
177  }
178 
182  public function uploadchoice(): void
183  {
184  $this->writePostData(true);
185  $this->editQuestion();
186  }
187 
191  public function removeimagechoice(): void
192  {
193  $this->writePostData(true);
194  $position = key($_POST['cmd']['removeimagechoice']);
195  $this->object->removeAnswerImage($position);
196  $this->editQuestion();
197  }
198 
202  public function addchoice(): void
203  {
204  $this->writePostData(true);
205  $position = key($_POST['cmd']['addchoice']);
206  $this->object->addAnswer("", 0, 0, $position + 1);
207  $this->editQuestion();
208  }
209 
213  public function removechoice(): void
214  {
215  $this->writePostData(true);
216  $position = key($_POST['cmd']['removechoice']);
217  $this->object->deleteAnswer($position);
218  $this->editQuestion();
219  }
220 
234  public function getSolutionOutput(
235  $active_id,
236  $pass = null,
237  $graphicalOutput = false,
238  $result_output = false,
239  $show_question_only = true,
240  $show_feedback = false,
241  $show_correct_solution = false,
242  $show_manual_scoring = false,
243  $show_question_text = true,
244  bool $show_inline_feedback = true
245  ): string {
246  if ($active_id > 0 && !$show_correct_solution) {
247  $user_solution = $this->object->getSolutionValues($active_id, $pass);
248  } else {
249  $user_solution = [];
250  foreach ($this->object->answers as $index => $answer) {
251  $points_checked = $answer->getPointsChecked();
252  $points_unchecked = $answer->getPointsUnchecked();
253  if ($points_checked > $points_unchecked && $points_checked > 0) {
254  $user_solution[] = ['value1' => $index];
255  }
256  }
257  }
258 
259  return $this->renderSolutionOutput(
260  $user_solution,
261  $active_id,
262  $pass,
263  $graphicalOutput,
264  $result_output,
265  $show_question_only,
266  $show_feedback,
267  $show_correct_solution,
268  $show_manual_scoring,
269  $show_question_text,
270  false,
271  $show_inline_feedback,
272  );
273  }
274 
275  public function renderSolutionOutput(
276  mixed $user_solutions,
277  int $active_id,
278  ?int $pass,
279  bool $graphical_output = false,
280  bool $result_output = false,
281  bool $show_question_only = true,
282  bool $show_feedback = false,
283  bool $show_correct_solution = false,
284  bool $show_manual_scoring = false,
285  bool $show_question_text = true,
286  bool $show_autosave_title = false,
287  bool $show_inline_feedback = false,
288  ): ?string {
289  $user_solution = [];
290 
291  foreach ($user_solutions as $idx => $solution_value) {
292  array_push($user_solution, $solution_value['value1']);
293  }
294 
295  $keys = $this->getChoiceKeys();
296  $template = new ilTemplate("tpl.il_as_qpl_mc_mr_output_solution.html", true, true, "Modules/TestQuestionPool");
297  $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "Modules/TestQuestionPool");
298  foreach ($keys as $answer_id) {
299  $answer = $this->object->answers[$answer_id];
300  if (($active_id > 0) && (!$show_correct_solution)) {
301  if ($graphical_output) {
302  // output of ok/not ok icons for user entered solutions
303  $ok = false;
304  $checked = false;
305  foreach ($user_solution as $mc_solution) {
306  if ((string) $mc_solution === (string) $answer_id) {
307  $checked = true;
308  }
309  }
310  if ($checked) {
311  if ($answer->getPointsChecked() > $answer->getPointsUnchecked()) {
312  $ok = self::CORRECTNESS_OK;
313  } else {
314  $ok = self::CORRECTNESS_NOT_OK;
315  }
316  } else {
317  if ($answer->getPointsChecked() > $answer->getPointsUnchecked()) {
318  $ok = self::CORRECTNESS_NOT_OK;
319  } else {
320  $ok = self::CORRECTNESS_OK;
321  }
322  }
323  $icon = $this->generateCorrectnessIconsForCorrectness($ok);
324  $template->setCurrentBlock("icon_ok");
325  $template->setVariable("ICON_OK", $icon);
326  $template->parseCurrentBlock();
327  }
328  }
329  if ($answer->hasImage()) {
330  $template->setCurrentBlock("answer_image");
331  if ($this->object->getThumbSize()) {
332  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $answer->getImage());
333  } else {
334  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $answer->getImage());
335  }
336  $alt = $answer->getImage();
337  if (strlen($answer->getAnswertext())) {
338  $alt = $answer->getAnswertext();
339  }
340  $alt = preg_replace("/<[^>]*?>/", "", $alt);
341  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
342  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
343  $template->parseCurrentBlock();
344  }
345 
346 
347  if (($show_feedback || !$this->isTestPresentationContext()) && $show_inline_feedback) {
348  if ($this->object->getSpecificFeedbackSetting() == 2) {
349  foreach ($user_solution as $mc_solution) {
350  if ((string) $mc_solution === (string) $answer_id) {
351  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
352  $this->object->getId(),
353  0,
354  $answer_id
355  );
356  if (strlen($fb)) {
357  $template->setCurrentBlock("feedback");
358  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
359  $template->parseCurrentBlock();
360  }
361  }
362  }
363  }
364 
365  if ($this->object->getSpecificFeedbackSetting() == 1) {
366  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
367  $this->object->getId(),
368  0,
369  $answer_id
370  );
371  if (strlen($fb)) {
372  $template->setCurrentBlock("feedback");
373  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
374  $template->parseCurrentBlock();
375  }
376  }
377  }
378  if ($show_feedback) {
379  if ($this->object->getSpecificFeedbackSetting() == 3) {
380  $answer = $this->object->getAnswer($answer_id);
381 
382  if ($answer->getPoints() > 0) {
383  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
384  $this->object->getId(),
385  0,
386  $answer_id
387  );
388  if (strlen($fb)) {
389  $template->setCurrentBlock("feedback");
390  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
391  $template->parseCurrentBlock();
392  }
393  }
394  }
395  }
396 
397 
398 
399  $template->setCurrentBlock("answer_row");
400  $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true));
401  $checked = false;
402  if ($result_output) {
403  $pointschecked = $this->object->answers[$answer_id]->getPointsChecked();
404  $pointsunchecked = $this->object->answers[$answer_id]->getPointsUnchecked();
405  $resulttextchecked = ($pointschecked == 1) || ($pointschecked == -1) ? "%s " . $this->lng->txt("point") : "%s " . $this->lng->txt("points");
406  $resulttextunchecked = ($pointsunchecked == 1) || ($pointsunchecked == -1) ? "%s " . $this->lng->txt("point") : "%s " . $this->lng->txt("points");
407  $template->setVariable("RESULT_OUTPUT", sprintf("(" . $this->lng->txt("checkbox_checked") . " = $resulttextchecked, " . $this->lng->txt("checkbox_unchecked") . " = $resulttextunchecked)", $pointschecked, $pointsunchecked));
408  }
409  foreach ($user_solution as $mc_solution) {
410  if ((string) $mc_solution === (string) $answer_id) {
411  if ($this->renderPurposeSupportsFormHtml() || $this->isRenderPurposePrintPdf()) {
412  $template->setVariable("SOLUTION_IMAGE", ilUtil::getHtmlPath(ilUtil::getImagePath("object/checkbox_checked.png")));
413  $template->setVariable("SOLUTION_ALT", $this->lng->txt("checked"));
414  } else {
415  $template->setVariable('QID', $this->object->getId());
416  $template->setVariable('SUFFIX', $show_correct_solution ? 'bestsolution' : 'usersolution');
417  $template->setVariable('SOLUTION_VALUE', $answer_id);
418  $template->setVariable('SOLUTION_CHECKED', 'checked');
419  }
420  $checked = true;
421  }
422  }
423  if (!$checked) {
424  if ($this->renderPurposeSupportsFormHtml() || $this->isRenderPurposePrintPdf()) {
425  $template->setVariable("SOLUTION_IMAGE", ilUtil::getHtmlPath(ilUtil::getImagePath("object/checkbox_unchecked.png")));
426  $template->setVariable("SOLUTION_ALT", $this->lng->txt("unchecked"));
427  } else {
428  $template->setVariable('QID', $this->object->getId());
429  $template->setVariable('SUFFIX', $show_correct_solution ? 'bestsolution' : 'usersolution');
430  $template->setVariable('SOLUTION_VALUE', $answer_id);
431  }
432  }
433  $template->parseCurrentBlock();
434  }
435  $questiontext = $this->object->getQuestionForHTMLOutput();
436  if ($show_feedback && $this->hasInlineFeedback()) {
437  $questiontext .= $this->buildFocusAnchorHtml();
438  }
439  if ($show_question_text == true) {
440  $template->setVariable("QUESTIONTEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($questiontext, true));
441  }
442  $questionoutput = $template->get();
443  $feedback = ($show_feedback && !$this->isTestPresentationContext()) ? $this->getGenericFeedbackOutput((int) $active_id, $pass) : "";
444 
445  if (strlen($feedback)) {
446  $cssClass = (
447  $this->hasCorrectSolution($active_id, $pass) ?
449  );
450 
451  $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
452  $solutiontemplate->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true));
453  }
454  $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
455 
456  $solutionoutput = $solutiontemplate->get();
457 
458  if (!$show_question_only) {
459  // get page object output
460  $solutionoutput = $this->getILIASPage($solutionoutput);
461  }
462  return $solutionoutput;
463  }
464 
465  public function getPreview($show_question_only = false, $showInlineFeedback = false): string
466  {
467  $user_solution = is_object($this->getPreviewSession()) ? (array) $this->getPreviewSession()->getParticipantsSolution() : [];
468  // shuffle output
469  $keys = $this->getChoiceKeys();
470 
471  $this->tpl->addOnLoadCode('ilAssMultipleChoiceCharCounterInit();');
472  $template = new ilTemplate("tpl.il_as_qpl_mc_mr_output.html", true, true, "Modules/TestQuestionPool");
473  foreach ($keys as $answer_id) {
474  $answer = $this->object->answers[$answer_id];
475  if ($answer->hasImage()) {
476  if ($this->object->getThumbSize()) {
477  $template->setCurrentBlock("preview");
478  $template->setVariable("URL_PREVIEW", $this->object->getImagePathWeb() . $answer->getImage());
479  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
480  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
481  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $answer->getImage());
482  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
483  $alt = $answer->getImage();
484  if (strlen($answer->getAnswertext())) {
485  $alt = $answer->getAnswertext();
486  }
487  $alt = preg_replace("/<[^>]*?>/", "", $alt);
488  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
489  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
490  $template->parseCurrentBlock();
491  } else {
492  $template->setCurrentBlock("answer_image");
493  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $answer->getImage());
494  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
495  $alt = $answer->getImage();
496  if (strlen($answer->getAnswertext())) {
497  $alt = $answer->getAnswertext();
498  }
499  $alt = preg_replace("/<[^>]*?>/", "", $alt);
500  $template->setVariable("ATTR", $attr);
501  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
502  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
503  $template->parseCurrentBlock();
504  }
505  }
506 
507  if ($showInlineFeedback) {
508  $this->populateSpecificFeedbackInline($user_solution, $answer_id, $template);
509  }
510 
511  $template->setCurrentBlock("answer_row");
512  $template->setVariable("QID", $this->object->getId());
513  $template->setVariable("ANSWER_ID", $answer_id);
514  $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true));
515  foreach ($user_solution as $mc_solution) {
516  if ((string) $mc_solution === (string) $answer_id) {
517  $template->setVariable("CHECKED_ANSWER", " checked=\"checked\"");
518  }
519  }
520  $template->parseCurrentBlock();
521  }
522  if ($this->object->getSelectionLimit()) {
523  $template->setVariable('SELECTION_LIMIT_HINT', sprintf(
524  $this->lng->txt('ass_mc_sel_lim_hint'),
525  $this->object->getSelectionLimit(),
526  $this->object->getAnswerCount()
527  ));
528 
529  $template->setVariable('SELECTION_LIMIT_VALUE', $this->object->getSelectionLimit());
530  } else {
531  $template->setVariable('SELECTION_LIMIT_VALUE', 'null');
532  }
533  $template->setVariable("QUESTION_ID", $this->object->getId());
534  $questiontext = $this->object->getQuestionForHTMLOutput();
535  if ($showInlineFeedback && $this->hasInlineFeedback()) {
536  $questiontext .= $this->buildFocusAnchorHtml();
537  }
538  $template->setVariable("QUESTIONTEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($questiontext, true));
539  $questionoutput = $template->get();
540  if (!$show_question_only) {
541  // get page object output
542  $questionoutput = $this->getILIASPage($questionoutput);
543  }
544  return $questionoutput;
545  }
546 
556  public function getTestOutput(
557  $active_id,
558  // hey: prevPassSolutions - will be always available from now on
559  $pass,
560  // hey.
561  $is_postponed = false,
562  $use_post_solutions = false,
563  $show_feedback = false
564  ): string {
565  // shuffle output
566  $keys = $this->getChoiceKeys();
567 
568  // get the solution of the user for the active pass or from the last pass if allowed
569  $user_solution = [];
570  if ($active_id) {
571  $solutions = $this->object->getTestOutputSolutions($active_id, $pass);
572  // hey.
573  foreach ($solutions as $idx => $solution_value) {
574  // fau: testNav - don't add the dummy entry for 'none of the above' to the user options
575  if ($solution_value['value1'] == 'mc_none_above') {
576  $this->setUseEmptySolutionInputChecked(true);
577  continue;
578  }
579 
580  $user_solution[] = $solution_value['value1'];
581  // fau.
582  }
583 
584  if (empty($user_solution) && $this->object->getTestPresentationConfig()->isWorkedThrough()) {
585  $this->setUseEmptySolutionInputChecked(true);
586  }
587  }
588  // generate the question output
589  $this->tpl->addJavaScript('Modules/TestQuestionPool/js/ilAssMultipleChoice.js');
590  $this->tpl->addOnLoadCode('ilAssMultipleChoiceCharCounterInit();');
591 
592  $template = new ilTemplate("tpl.il_as_qpl_mc_mr_output.html", true, true, "Modules/TestQuestionPool");
593  foreach ($keys as $answer_id) {
594  $answer = $this->object->answers[$answer_id];
595  if ($answer->hasImage()) {
596  if ($this->object->getThumbSize()) {
597  $template->setCurrentBlock("preview");
598  $template->setVariable("URL_PREVIEW", $this->object->getImagePathWeb() . $answer->getImage());
599  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
600  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
601  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $answer->getImage());
602  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
603  $alt = $answer->getImage();
604  if (strlen($answer->getAnswertext())) {
605  $alt = $answer->getAnswertext();
606  }
607  $alt = preg_replace("/<[^>]*?>/", "", $alt);
608  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
609  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
610  $template->parseCurrentBlock();
611  } else {
612  $template->setCurrentBlock("answer_image");
613  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $answer->getImage());
614  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
615  $alt = $answer->getImage();
616  if (strlen($answer->getAnswertext())) {
617  $alt = $answer->getAnswertext();
618  }
619  $alt = preg_replace("/<[^>]*?>/", "", $alt);
620  $template->setVariable("ATTR", $attr);
621  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
622  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
623  $template->parseCurrentBlock();
624  }
625  }
626 
627  if ($show_feedback) {
628  $this->populateSpecificFeedbackInline($user_solution, $answer_id, $template);
629  }
630 
631  $template->setCurrentBlock("answer_row");
632  $template->setVariable("QID", $this->object->getId());
633  $template->setVariable("ANSWER_ID", $answer_id);
634  $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true));
635  foreach ($user_solution as $mc_solution) {
636  if ((string) $mc_solution === (string) $answer_id) {
637  $template->setVariable("CHECKED_ANSWER", " checked=\"checked\"");
638  }
639  }
640  $template->parseCurrentBlock();
641  }
642 
643  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
644  $template->setVariable("QUESTION_ID", $this->object->getId());
645  if ($this->object->getSelectionLimit()) {
646  $template->setVariable('SELECTION_LIMIT_HINT', sprintf(
647  $this->lng->txt('ass_mc_sel_lim_hint'),
648  $this->object->getSelectionLimit(),
649  $this->object->getAnswerCount()
650  ));
651 
652  $template->setVariable('SELECTION_LIMIT_VALUE', $this->object->getSelectionLimit());
653  } else {
654  $template->setVariable('SELECTION_LIMIT_VALUE', 'null');
655  }
656  $questionoutput = $template->get();
657  $pageoutput = $this->outQuestionPage("", $is_postponed, $active_id, $questionoutput, $show_feedback);
658  return $pageoutput;
659  }
660 
661  protected $useEmptySolutionInputChecked = false;
662 
663  public function isUseEmptySolutionInputChecked(): bool
664  {
666  }
667 
669  {
670  $this->useEmptySolutionInputChecked = $useEmptySolutionInputChecked;
671  }
672 
673  protected function getUseUnchangedAnswerCheckboxHtml(): string
674  {
675  // hey: prevPassSolutions - use abstracted template to share with other purposes of this kind
676  $this->tpl->addJavaScript('Modules/TestQuestionPool/js/ilAssMultipleChoice.js');
677 
678  $tpl = new ilTemplate('tpl.tst_question_additional_behaviour_checkbox.html', true, true, 'Modules/TestQuestionPool');
679 
680  // HEY: affects next if (!) /// noneAboveChecked repaired but disabled because the checked input ..
681  if (false) { // .. makes the qstEditController initialize the "edit" instead of the "answered" state
682  if ($this->isUseEmptySolutionInputChecked()) {
683  $tpl->setCurrentBlock('checked');
684  $tpl->touchBlock('checked');
686  }
687  }
688 
689  $tpl->setCurrentBlock('checkbox');
690  $tpl->setVariable('TXT_FORCE_FORM_DIFF_LABEL', $this->object->getTestPresentationConfig()->getUseUnchangedAnswerLabel());
692  // hey.
693  return $tpl->get();
694  }
695 
696  public function getPresentationJavascripts(): array
697  {
698  return ['Modules/TestQuestionPool/js/ilAssMultipleChoice.js'];
699  }
700 
706  public function getChoiceKeys(): array
707  {
708  $choice_keys = array_keys($this->object->answers);
709 
710  if ($this->object->getShuffle()) {
711  $choice_keys = $this->object->getShuffler()->transform($choice_keys);
712  }
713 
714  return $choice_keys;
715  }
716 
717  public function getSpecificFeedbackOutput(array $userSolution): string
718  {
719  // No return value, this question type supports inline specific feedback.
720  $output = "";
722  }
723 
725  {
726  $this->object->setShuffle($_POST["shuffle"] ?? '0');
727 
728  $selectionLimit = (int) $form->getItemByPostVar('selection_limit')->getValue();
729  $this->object->setSelectionLimit($selectionLimit > 0 ? $selectionLimit : null);
730 
731  if (isset($_POST['feedback_setting'])) {
732  $this->object->setSpecificFeedbackSetting($_POST['feedback_setting']);
733  }
734 
735  $types = (int) ($_POST['types'] ?? '0');
736  $this->object->setMultilineAnswerSetting($types);
737  if (isset($_POST['choice']['imagename']) && is_array($_POST['choice']['imagename']) && $types === 1) {
738  $this->object->setIsSingleline(true);
739  $this->tpl->setOnScreenMessage('info', $this->lng->txt('info_answer_type_change'), true);
740  } else {
741  $this->object->setIsSingleline(($types === 0) ? true : false);
742  }
743  if (isset($_POST["thumb_size"])
744  && (int) $_POST["thumb_size"] !== $this->object->getThumbSize()) {
745  $this->object->setThumbSize((int) $_POST["thumb_size"]);
746  $this->rebuild_thumbnails = true;
747  }
748  }
749 
750  public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void
751  {
752  // Delete all existing answers and create new answers from the form data
753  $this->object->flushAnswers();
754  $choice = $this->cleanupAnswerText($_POST['choice'], $this->object->isSingleline() === false);
755  if (!$this->object->isSingleline()) {
756  foreach ($choice['answer'] as $index => $answer) {
757  $answertext = $answer;
758  $this->object->addAnswer(
759  $answertext,
760  (float) str_replace(',', '.', $choice['points'][$index]),
761  (float) str_replace(',', '.', $choice['points_unchecked'][$index]),
762  $index,
763  null,
764  (int) $choice['answer_id'][$index]
765  );
766  }
767  return;
768  }
769 
770  foreach ($choice['answer'] as $index => $answertext) {
771  $answertext = htmlentities($answertext);
772  $picturefile = $choice['imagename'][$index] ?? null;
773  $file_org_name = $_FILES['choice']['name']['image'][$index] ?? '';
774  $file_temp_name = $_FILES['choice']['tmp_name']['image'][$index] ?? '';
775 
776  if ($file_temp_name !== '') {
777  // check suffix
778  $parts = explode(".", $file_org_name);
779  $suffix = strtolower(array_pop($parts));
780  if (in_array($suffix, ["jpg", "jpeg", "png", "gif"])) {
781  // upload image
782  $filename = $this->object->buildHashedImageFilename($file_org_name);
783  if ($this->object->setImageFile($filename, $file_temp_name) == 0) {
784  $picturefile = $filename;
785  }
786  }
787  }
788 
789  $this->object->addAnswer(
790  $answertext,
791  (float) str_replace(',', '.', $choice['points'][$index]),
792  (float) str_replace(',', '.', $choice['points_unchecked'][$index]),
793  $index,
794  $picturefile,
795  $choice['answer_id'][$index]
796  );
797  }
798 
799  if ($this->rebuild_thumbnails) {
800  $this->object->setAnswers(
801  $this->object->rebuildThumbnails(
802  $this->object->isSingleline(),
803  $this->object->getThumbSize(),
804  $this->object->getImagePath(),
805  $this->object->getAnswers()
806  )
807  );
808  }
809  }
810 
811  public function populateQuestionSpecificFormPart(\ilPropertyFormGUI $form, bool $is_singleline = false): ilPropertyFormGUI
812  {
813  // shuffle
814  $shuffle = new ilCheckboxInputGUI($this->lng->txt("shuffle_answers"), "shuffle");
815  $shuffle->setValue(1);
816  $shuffle->setChecked($this->object->getShuffle());
817  $shuffle->setRequired(false);
818  $form->addItem($shuffle);
819 
820  $selLim = new ilNumberInputGUI($this->lng->txt('ass_mc_sel_lim_setting'), 'selection_limit');
821  $selLim->setInfo($this->lng->txt('ass_mc_sel_lim_setting_desc'));
822  $selLim->setSize(2);
823  $selLim->setRequired(false);
824  $selLim->allowDecimals(false);
825  $selLim->setMinvalueShouldBeGreater(false);
826  $selLim->setMaxvalueShouldBeLess(false);
827  $selLim->setMinValue(1);
828  $selLim->setMaxValue($this->object->getAnswerCount());
829  $selLim->setValue($this->object->getSelectionLimit());
830  $form->addItem($selLim);
831 
832  if ($this->object->getId()) {
833  $hidden = new ilHiddenInputGUI("ID");
834  $hidden->setValue($this->object->getId());
835  $form->addItem($hidden);
836  }
837 
838  if (!$this->object->getSelfAssessmentEditingMode()) {
839  // Answer types
840  $types = new ilSelectInputGUI($this->lng->txt("answer_types"), "types");
841  $types->setRequired(false);
842  $types->setValue(($is_singleline) ? 0 : 1);
843  $types->setOptions([
844  0 => $this->lng->txt('answers_singleline'),
845  1 => $this->lng->txt('answers_multiline'),
846  ]);
847  $form->addItem($types);
848  }
849 
850  if ($is_singleline) {
851  // thumb size
852  $thumb_size = new ilNumberInputGUI($this->lng->txt('thumb_size'), "thumb_size");
853  $thumb_size->setSuffix($this->lng->txt('thumb_size_unit_pixel'));
854  $thumb_size->setMinValue($this->object->getMinimumThumbSize());
855  $thumb_size->setMaxValue($this->object->getMaximumThumbSize());
856  $thumb_size->setDecimals(0);
857  $thumb_size->setSize(6);
858  $thumb_size->setInfo($this->lng->txt('thumb_size_info'));
859  $thumb_size->setValue($this->object->getThumbSize());
860  $thumb_size->setRequired(true);
861  } else {
862  $thumb_size = new ilHiddenInputGUI('thumb_size');
863  $thumb_size->setValue($this->object->getThumbSize());
864  }
865  $form->addItem($thumb_size);
866 
867  return $form;
868  }
869 
870  public function populateAnswerSpecificFormPart(\ilPropertyFormGUI $form, bool $is_singleline = false): ilPropertyFormGUI
871  {
872  $choices = new ilMultipleChoiceWizardInputGUI($this->lng->txt("answers"), "choice");
873  $choices->setRequired(true);
874  $choices->setQuestionObject($this->object);
875  $choices->setSingleline($is_singleline);
876  $choices->setAllowMove(false);
877  if ($this->object->getSelfAssessmentEditingMode()) {
878  $choices->setSize(40);
879  }
880  if ($this->object->getAnswerCount() == 0) {
881  $this->object->addAnswer("", 0, 0, 0);
882  }
883  $choices->setValues(array_map(
884  function (ASS_AnswerMultipleResponseImage $value) {
885  $value->setAnswerText(html_entity_decode($value->getAnswerText()));
886  return $value;
887  },
888  $this->object->getAnswers()
889  ));
890  $form->addItem($choices);
891  return $form;
892  }
893 
904  {
905  return [];
906  }
907 
918  {
919  return [];
920  }
921 
928  public function getAggregatedAnswersView(array $relevant_answers): string
929  {
930  return $this->renderAggregateView(
931  $this->aggregateAnswers($relevant_answers, $this->object->getAnswers())
932  )->get();
933  }
934 
935  public function aggregateAnswers($relevant_answers_chosen, $answers_defined_on_question): array
936  {
937  $aggregate = [];
938  foreach ($answers_defined_on_question as $answer) {
939  $aggregated_info_for_answer = [];
940  $aggregated_info_for_answer['answertext'] = $answer->getAnswerText();
941  $aggregated_info_for_answer['count_checked'] = 0;
942 
943  foreach ($relevant_answers_chosen as $relevant_answer) {
944  if ($relevant_answer['value1'] == $answer->getOrder()) {
945  $aggregated_info_for_answer['count_checked']++;
946  }
947  }
948  $aggregated_info_for_answer['count_unchecked'] =
949  ceil(count($relevant_answers_chosen) / count($answers_defined_on_question))
950  - $aggregated_info_for_answer['count_checked'];
951 
952  $aggregate[] = $aggregated_info_for_answer;
953  }
954  return $aggregate;
955  }
956 
962  public function renderAggregateView($aggregate): ilTemplate
963  {
964  $tpl = new ilTemplate('tpl.il_as_aggregated_answers_table.html', true, true, "Modules/TestQuestionPool");
965 
966  $tpl->setCurrentBlock('headercell');
967  $tpl->setVariable('HEADER', $this->lng->txt('tst_answer_aggr_answer_header'));
969 
970  $tpl->setCurrentBlock('headercell');
971  $tpl->setVariable('HEADER', $this->lng->txt('tst_answer_aggr_frequency_header'));
973 
974  foreach ($aggregate as $line_data) {
975  $tpl->setCurrentBlock('aggregaterow');
976  $tpl->setVariable('OPTION', $line_data['answertext']);
977  $tpl->setVariable('COUNT', $line_data['count_checked']);
979  }
980  return $tpl;
981  }
982 
983  private function populateSpecificFeedbackInline($user_solution, $answer_id, $template): void
984  {
985  if ($this->object->getSpecificFeedbackSetting() == 2) {
986  foreach ($user_solution as $mc_solution) {
987  if ((string) $mc_solution === (string) $answer_id) {
988  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id);
989  if (strlen($fb)) {
990  $template->setCurrentBlock("feedback");
991  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
992  $template->parseCurrentBlock();
993  }
994  }
995  }
996  }
997 
998  if ($this->object->getSpecificFeedbackSetting() == 1) {
999  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id);
1000  if (strlen($fb)) {
1001  $template->setCurrentBlock("feedback");
1002  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
1003  $template->parseCurrentBlock();
1004  }
1005  }
1006 
1007  if ($this->object->getSpecificFeedbackSetting() == 3) {
1008  $answer = $this->object->getAnswer($answer_id);
1009 
1010  if ($answer->getPoints() > 0) {
1011  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id);
1012  if (strlen($fb)) {
1013  $template->setCurrentBlock("feedback");
1014  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
1015  $template->parseCurrentBlock();
1016  }
1017  }
1018  }
1019  }
1020 
1021  // fau: testNav - new functions setWithNoneAbove() and setIsAnswered()
1022  // moved functionality to ilTestQuestionPresentationConfig
1023  // fau.
1024 
1028  protected function buildEditForm(bool $is_singleline = true): ilPropertyFormGUI
1029  {
1030  $form = new ilPropertyFormGUI();
1031  $form->setFormAction($this->ctrl->getFormAction($this));
1032  $form->setTitle($this->outQuestionType());
1033  $form->setTableWidth("100%");
1034  $form->setId("assmultiplechoice");
1035 
1036  $this->addBasicQuestionFormProperties($form);
1037  $this->populateQuestionSpecificFormPart($form, $is_singleline);
1038  $this->populateAnswerSpecificFormPart($form, $is_singleline);
1039  $this->populateTaxonomyFormSection($form);
1040  $this->addQuestionFormCommandButtons($form);
1041  return $form;
1042  }
1043 
1044  public function getAnswersFrequency($relevantAnswers, $questionIndex): array
1045  {
1046  $agg = $this->aggregateAnswers($relevantAnswers, $this->object->getAnswers());
1047 
1048  $answers = [];
1049 
1050  foreach ($agg as $ans) {
1051  $answers[] = [
1052  'answer' => $ans['answertext'],
1053  'frequency' => $ans['count_checked']
1054  ];
1055  }
1056 
1057  return $answers;
1058  }
1059 
1061  {
1062  $choices = new ilAssMultipleChoiceCorrectionsInputGUI($this->lng->txt("answers"), "choice");
1063  $choices->setRequired(true);
1064  $choices->setQuestionObject($this->object);
1065  $choices->setValues($this->object->getAnswers());
1066  $form->addItem($choices);
1067  }
1068 
1073  {
1074  $input = $form->getItemByPostVar('choice');
1075  $answerElements = $input->getValues();
1076 
1077  foreach ($this->object->getAnswers() as $index => $answer) {
1078  /* @var ASS_AnswerMultipleResponseImage $answer */
1079  $answer->setPointsChecked((float) str_replace(',', '.', $answerElements[$index]->getPointsChecked()));
1080  $answer->setPointsUnchecked((float) str_replace(',', '.', $answerElements[$index]->getPointsUnchecked()));
1081  }
1082  }
1083 }
hasCorrectSolution($activeId, $passIndex)
generateCorrectnessIconsForCorrectness(int $correctness)
setId(string $a_id)
setCurrentBlock(string $blockname=self::DEFAULT_BLOCK)
Sets the template to the given block.
getAnswersFrequency($relevantAnswers, $questionIndex)
This class represents a selection list property in a property form.
getItemByPostVar(string $a_post_var)
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
setUseEmptySolutionInputChecked($useEmptySolutionInputChecked)
getAfterParticipationSuppressionAnswerPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
getChoiceKeys()
Create the key index numbers for the array of choices.
static getImagePath(string $img, string $module_path="", string $mode="output", bool $offline=false)
get image path (for images located in a template directory)
ASS_AnswerBinaryStateImage is a class for answers with a binary state indicator (checked/unchecked, set/unset) and an image file.
populateQuestionSpecificFormPart(\ilPropertyFormGUI $form, bool $is_singleline=false)
populateAnswerSpecificFormPart(\ilPropertyFormGUI $form, bool $is_singleline=false)
parseCurrentBlock(string $blockname=self::DEFAULT_BLOCK)
Parses the given block.
saveCorrectionsFormProperties(ilPropertyFormGUI $form)
getSolutionOutput( $active_id, $pass=null, $graphicalOutput=false, $result_output=false, $show_question_only=true, $show_feedback=false, $show_correct_solution=false, $show_manual_scoring=false, $show_question_text=true, bool $show_inline_feedback=true)
Get the question solution output.
ilGlobalPageTemplate $tpl
static prepareFormOutput($a_str, bool $a_strip=false)
Class for multiple choice tests.
populateTaxonomyFormSection(ilPropertyFormGUI $form)
removeimagechoice()
Remove an image.
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
editQuestion($checkonly=false)
Creates an output of the edit form for the question.
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
__construct(VocabulariesInterface $vocabularies)
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
setFormAction(string $a_formaction)
writeAnswerSpecificPostData(ilPropertyFormGUI $form)
Extracts the answer specific values from $_POST and applies them to the data object.
getPreview($show_question_only=false, $showInlineFeedback=false)
static getHtmlPath(string $relative_path)
get url of path
writeQuestionSpecificPostData(ilPropertyFormGUI $form)
Extracts the question specific values from $_POST and applies them to the data object.
Basic GUI class for assessment questions.
setRequired(bool $a_required)
aggregateAnswers($relevant_answers_chosen, $answers_defined_on_question)
setTableWidth(string $a_width)
$filename
Definition: buildRTE.php:78
getAfterParticipationSuppressionQuestionPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
touchBlock(string $blockname)
overwrites ITX::touchBlock.
getEditAnswersSingleLine($checkonly=false)
Get the single/multiline editing of answers.
__construct($id=-1)
assMultipleChoiceGUI constructor
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
writePostData(bool $always=false)
{}
getILIASPage(string $html="")
Returns the ILIAS Page around a question.
outQuestionPage($a_temp_var, $a_postponed=false, $active_id="", $html="", $inlineFeedbackEnabled=false)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
cleanupAnswerText(array $answer_text, bool $is_rte)
sk - 12.05.2023: This is one more of those that we need, but don&#39;t want.
getTestOutput( $active_id, $pass, $is_postponed=false, $use_post_solutions=false, $show_feedback=false)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
buildEditForm(bool $is_singleline=true)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static prepareTextareaOutput(string $txt_output, bool $prepare_for_latex_output=false, bool $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free...
getSpecificFeedbackOutput(array $userSolution)
populateSpecificFeedbackInline($user_solution, $answer_id, $template)
setVariable(string $variable, $value='')
Sets the given variable to the given value.
getAggregatedAnswersView(array $relevant_answers)
Returns an html string containing a question specific representation of the answers so far given in t...
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
renderSolutionOutput(mixed $user_solutions, int $active_id, ?int $pass, bool $graphical_output=false, bool $result_output=false, bool $show_question_only=true, bool $show_feedback=false, bool $show_correct_solution=false, bool $show_manual_scoring=false, bool $show_question_text=true, bool $show_autosave_title=false, bool $show_inline_feedback=false,)
getGenericFeedbackOutput(int $active_id, ?int $pass)