ILIAS  trunk Revision v11.0_alpha-1689-g66c127b4ae8
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.assMultipleChoiceGUI.php
Go to the documentation of this file.
1 <?php
2 
34 {
35  private bool $rebuild_thumbnails = false;
36 
45  public function __construct($id = -1)
46  {
48  $this->object = new assMultipleChoice();
49  if ($id >= 0) {
50  $this->object->loadFromDb($id);
51  }
52  }
53 
57  public function hasInlineFeedback(): bool
58  {
59  return $this->object->feedbackOBJ->isSpecificAnswerFeedbackAvailable($this->object->getId());
60  }
61 
65  protected function writePostData(bool $always = false): int
66  {
67  $hasErrors = (!$always) ? $this->editQuestion(true) : false;
68  if (!$hasErrors) {
69  $is_singleline = $this->getEditAnswersSingleLine();
70  $form = $this->buildEditForm($is_singleline);
71  $form->setValuesByPost();
73  $this->writeQuestionSpecificPostData($form);
74  $this->writeAnswerSpecificPostData($form);
75  $this->saveTaxonomyAssignments();
76  return 0;
77  }
78  return 1;
79  }
80 
88  protected function getEditAnswersSingleLine($checkonly = false): bool
89  {
90  if ($this->object->getSelfAssessmentEditingMode()) {
91  return $this->object->isSingleline();
92  }
93 
94  if ($checkonly) {
95  return $this->request_data_collector->int('types') === 0;
96  }
97 
98  if (empty($this->object->getLastChange())
99  && !$this->request_data_collector->isset('types')) {
100  // a new question is edited
101  return $this->object->getMultilineAnswerSetting() === 0;
102  }
103  // a saved question is edited
104  return $this->object->isSingleline();
105  }
106 
107  public function editQuestion(
108  bool $checkonly = false,
109  ?bool $is_save_cmd = null
110  ): bool {
111  $save = $is_save_cmd ?? $this->isSaveCommand();
112 
113  $is_singleline = $this->getEditAnswersSingleLine($checkonly);
114 
115  $form = $this->buildEditForm($is_singleline);
116 
117  if ($is_singleline) {
118  $form->setMultipart(true);
119  } else {
120  $form->setMultipart(false);
121  }
122 
123  $errors = false;
124 
125  if ($save) {
126  $form->getItemByPostVar('selection_limit')->setMaxValue(count($this->request_data_collector->raw('choice')['answer'] ?? []));
127 
128  $form->setValuesByPost();
129  $errors = !$this->checkMaxPointsNotNegative($form) || !$form->checkInput();
130  if ($errors) {
131  $checkonly = false;
132  }
133  }
134 
135  if (!$checkonly) {
136  $this->renderEditForm($form);
137  }
138  return $errors;
139  }
140 
141  private function checkMaxPointsNotNegative(ilPropertyFormGUI $form): bool
142  {
143  $choice = $form->getItemByPostVar('choice');
144  if (!$choice instanceof ilMultipleChoiceWizardInputGUI) {
145  return true;
146  }
147 
148  $answers = $choice->getValues();
149  $total_max_points = 0;
151  foreach ($answers as $answer) {
152  $total_max_points += max($answer->getPointsChecked(), $answer->getPointsUnchecked());
153  }
154 
155  if ($total_max_points < 0) {
156  $choice->setAlert($this->lng->txt('total_max_points_cannot_be_negative'));
157  return false;
158  }
159 
160  return true;
161  }
162 
164  {
165  parent::addBasicQuestionFormProperties($form);
166  $form->getItemByPostVar('question')->setInitialRteWidth('100');
167  }
168 
169  public function uploadchoice(): void
170  {
172  $this->writePostData(true);
173  $this->editQuestion();
174  }
175 
176  public function removeimagechoice(): void
177  {
179  $this->writePostData(true);
180  $this->object->removeAnswerImage($this->request_data_collector->getCmdIndex('removeimagechoice'));
181  $this->editQuestion();
182  }
183 
184  public function addchoice(): void
185  {
186  $this->writePostData(true);
187  $position = $this->request_data_collector->getCmdIndex('addchoice');
188  $this->object->addAnswer("", 0, $position + 1);
189  $this->editQuestion();
190  }
191 
192  public function removechoice(): void
193  {
194  $this->writePostData(true);
195  $this->object->deleteAnswer($this->request_data_collector->getCmdIndex('removechoice'));
196  $this->editQuestion();
197  }
198 
199  public function getSolutionOutput(
200  int $active_id,
201  ?int $pass = null,
202  bool $graphical_output = false,
203  bool $result_output = false,
204  bool $show_question_only = true,
205  bool $show_feedback = false,
206  bool $show_correct_solution = false,
207  bool $show_manual_scoring = false,
208  bool $show_question_text = true,
209  bool $show_inline_feedback = true
210  ): string {
211  if ($active_id > 0 && !$show_correct_solution) {
212  $user_solution = $this->object->getSolutionValues($active_id, $pass);
213  } else {
214  $user_solution = [];
215  foreach ($this->object->answers as $index => $answer) {
216  $points_checked = $answer->getPointsChecked();
217  $points_unchecked = $answer->getPointsUnchecked();
218  if ($points_checked > $points_unchecked && $points_checked > 0) {
219  $user_solution[] = ['value1' => $index];
220  }
221  }
222  }
223 
224  return $this->renderSolutionOutput(
225  $user_solution,
226  $active_id,
227  $pass,
228  $graphical_output,
229  $result_output,
230  $show_question_only,
231  $show_feedback,
232  $show_correct_solution,
233  $show_manual_scoring,
234  $show_question_text,
235  false,
236  $show_inline_feedback,
237  );
238  }
239 
240  public function renderSolutionOutput(
241  mixed $user_solutions,
242  int $active_id,
243  ?int $pass,
244  bool $graphical_output = false,
245  bool $result_output = false,
246  bool $show_question_only = true,
247  bool $show_feedback = false,
248  bool $show_correct_solution = false,
249  bool $show_manual_scoring = false,
250  bool $show_question_text = true,
251  bool $show_autosave_title = false,
252  bool $show_inline_feedback = false,
253  ): ?string {
254  $user_solution = [];
255 
256  foreach ($user_solutions as $idx => $solution_value) {
257  array_push($user_solution, $solution_value["value1"]);
258  }
259 
260  $template = new ilTemplate("tpl.il_as_qpl_mc_mr_output_solution.html", true, true, "components/ILIAS/TestQuestionPool");
261  $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "components/ILIAS/TestQuestionPool");
262  $keys = $this->getChoiceKeys();
263  foreach ($keys as $answer_id) {
264  $answer = $this->object->answers[$answer_id];
265  if (($active_id > 0) && (!$show_correct_solution)) {
266  if ($graphical_output) {
267  // output of ok/not ok icons for user entered solutions
268  $ok = false;
269  $checked = false;
270  foreach ($user_solution as $mc_solution) {
271  if (strcmp($mc_solution, $answer_id) == 0) {
272  $checked = true;
273  }
274  }
275  if ($checked) {
276  if ($answer->getPointsChecked() > $answer->getPointsUnchecked()) {
277  $ok = self::CORRECTNESS_OK;
278  } else {
279  $ok = self::CORRECTNESS_NOT_OK;
280  }
281  } else {
282  if ($answer->getPointsChecked() > $answer->getPointsUnchecked()) {
283  $ok = self::CORRECTNESS_NOT_OK;
284  } else {
285  $ok = self::CORRECTNESS_OK;
286  }
287  }
288  $icon = $this->generateCorrectnessIconsForCorrectness($ok);
289  $template->setCurrentBlock("icon_ok");
290  $template->setVariable("ICON_OK", $icon);
291  $template->parseCurrentBlock();
292  }
293  }
294  if ($answer->hasImage()) {
295  $template->setCurrentBlock("answer_image");
296  if ($this->object->getThumbSize()) {
297  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $answer->getImage());
298  } else {
299  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $answer->getImage());
300  }
301  $alt = $answer->getImage();
302  if (strlen($answer->getAnswertext())) {
303  $alt = $answer->getAnswertext();
304  }
305  $alt = preg_replace("/<[^>]*?>/", "", $alt);
306  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
307  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
308  $template->parseCurrentBlock();
309  }
310 
311 
312  if (($show_feedback || !$this->isTestPresentationContext()) && $show_inline_feedback) {
313  if ($this->object->getSpecificFeedbackSetting() == 2) {
314  foreach ($user_solution as $mc_solution) {
315  if (strcmp($mc_solution, $answer_id) == 0) {
316  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
317  $this->object->getId(),
318  0,
319  $answer_id
320  );
321  if (strlen($fb)) {
322  $template->setCurrentBlock("feedback");
323  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
324  $template->parseCurrentBlock();
325  }
326  }
327  }
328  }
329 
330  if ($this->object->getSpecificFeedbackSetting() == 1) {
331  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
332  $this->object->getId(),
333  0,
334  $answer_id
335  );
336  if (strlen($fb)) {
337  $template->setCurrentBlock("feedback");
338  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
339  $template->parseCurrentBlock();
340  }
341  }
342  }
343  if ($show_feedback) {
344  if ($this->object->getSpecificFeedbackSetting() == 3) {
345  $answer = $this->object->getAnswer($answer_id);
346 
347  if ($answer->getPoints() > 0) {
348  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation(
349  $this->object->getId(),
350  0,
351  $answer_id
352  );
353  if (strlen($fb)) {
354  $template->setCurrentBlock("feedback");
355  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
356  $template->parseCurrentBlock();
357  }
358  }
359  }
360  }
361 
362 
363 
364  $template->setCurrentBlock("answer_row");
365  $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true));
366  $checked = false;
367  if ($result_output) {
368  $pointschecked = $this->object->answers[$answer_id]->getPointsChecked();
369  $pointsunchecked = $this->object->answers[$answer_id]->getPointsUnchecked();
370  $resulttextchecked = ($pointschecked == 1) || ($pointschecked == -1) ? "%s " . $this->lng->txt("point") : "%s " . $this->lng->txt("points");
371  $resulttextunchecked = ($pointsunchecked == 1) || ($pointsunchecked == -1) ? "%s " . $this->lng->txt("point") : "%s " . $this->lng->txt("points");
372  $template->setVariable("RESULT_OUTPUT", sprintf("(" . $this->lng->txt("checkbox_checked") . " = $resulttextchecked, " . $this->lng->txt("checkbox_unchecked") . " = $resulttextunchecked)", $pointschecked, $pointsunchecked));
373  }
374  foreach ($user_solution as $mc_solution) {
375  if (strcmp($mc_solution, $answer_id) == 0) {
376  if ($this->renderPurposeSupportsFormHtml() || $this->isRenderPurposePrintPdf()) {
377  $template->setVariable("SOLUTION_IMAGE", ilUtil::getHtmlPath(ilUtil::getImagePath("object/checkbox_checked.png")));
378  $template->setVariable("SOLUTION_ALT", $this->lng->txt("checked"));
379  } else {
380  $template->setVariable('QID', $this->object->getId());
381  $template->setVariable('SUFFIX', $show_correct_solution ? 'bestsolution' : 'usersolution');
382  $template->setVariable('SOLUTION_VALUE', $answer_id);
383  $template->setVariable('SOLUTION_CHECKED', 'checked');
384  }
385  $checked = true;
386  }
387  }
388  if (!$checked) {
389  if ($this->renderPurposeSupportsFormHtml() || $this->isRenderPurposePrintPdf()) {
390  $template->setVariable("SOLUTION_IMAGE", ilUtil::getHtmlPath(ilUtil::getImagePath("object/checkbox_unchecked.png")));
391  $template->setVariable("SOLUTION_ALT", $this->lng->txt("unchecked"));
392  } else {
393  $template->setVariable('QID', $this->object->getId());
394  $template->setVariable('SUFFIX', $show_correct_solution ? 'bestsolution' : 'usersolution');
395  $template->setVariable('SOLUTION_VALUE', $answer_id);
396  }
397  }
398  $template->parseCurrentBlock();
399  }
400  $questiontext = $this->object->getQuestionForHTMLOutput();
401  if ($show_feedback && $this->hasInlineFeedback()) {
402  $questiontext .= $this->buildFocusAnchorHtml();
403  }
404  if ($show_question_text == true) {
405  $template->setVariable("QUESTIONTEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($questiontext, true));
406  }
407  $questionoutput = $template->get();
408  $feedback = ($show_feedback && !$this->isTestPresentationContext()) ? $this->getGenericFeedbackOutput((int) $active_id, $pass) : "";
409 
410  if (strlen($feedback)) {
411  $cssClass = (
412  $this->hasCorrectSolution($active_id, $pass) ?
414  );
415 
416  $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
417  $solutiontemplate->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true));
418  }
419  $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
420 
421  $solutionoutput = $solutiontemplate->get();
422 
423  if (!$show_question_only) {
424  // get page object output
425  $solutionoutput = $this->getILIASPage($solutionoutput);
426  }
427  return $solutionoutput;
428  }
429 
430  public function getPreview(
431  bool $show_question_only = false,
432  bool $show_inline_feedback = false
433  ): string {
434  $user_solution = is_object($this->getPreviewSession()) ? (array) $this->getPreviewSession()->getParticipantsSolution() : [];
435  // shuffle output
436  $keys = $this->getChoiceKeys();
437 
438  $this->tpl->addOnLoadCode('ilAssMultipleChoiceCharCounterInit();');
439  $template = new ilTemplate("tpl.il_as_qpl_mc_mr_output.html", true, true, "components/ILIAS/TestQuestionPool");
440  foreach ($keys as $answer_id) {
441  $answer = $this->object->answers[$answer_id];
442  if ($answer->hasImage()) {
443  if ($this->object->getThumbSize()) {
444  $template->setCurrentBlock("preview");
445  $template->setVariable("URL_PREVIEW", $this->object->getImagePathWeb() . $answer->getImage());
446  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
447  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
448  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $answer->getImage());
449  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
450  $alt = $answer->getImage();
451  if (strlen($answer->getAnswertext())) {
452  $alt = $answer->getAnswertext();
453  }
454  $alt = preg_replace("/<[^>]*?>/", "", $alt);
455  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
456  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
457  $template->parseCurrentBlock();
458  } else {
459  $template->setCurrentBlock("answer_image");
460  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $answer->getImage());
461  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
462  $alt = $answer->getImage();
463  if (strlen($answer->getAnswertext())) {
464  $alt = $answer->getAnswertext();
465  }
466  $alt = preg_replace("/<[^>]*?>/", "", $alt);
467  $template->setVariable("ATTR", $attr);
468  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
469  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
470  $template->parseCurrentBlock();
471  }
472  }
473 
474  if ($show_inline_feedback) {
475  $this->populateSpecificFeedbackInline($user_solution, $answer_id, $template);
476  }
477 
478  $template->setCurrentBlock("answer_row");
479  $template->setVariable("QID", $this->object->getId());
480  $template->setVariable("ANSWER_ID", $answer_id);
481  $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true));
482  foreach ($user_solution as $mc_solution) {
483  if (strcmp($mc_solution, $answer_id) == 0) {
484  $template->setVariable("CHECKED_ANSWER", " checked=\"checked\"");
485  }
486  }
487  $template->parseCurrentBlock();
488  }
489  if ($this->object->getSelectionLimit()) {
490  $template->setVariable('SELECTION_LIMIT_HINT', sprintf(
491  $this->lng->txt('ass_mc_sel_lim_hint'),
492  $this->object->getSelectionLimit(),
493  $this->object->getAnswerCount()
494  ));
495 
496  $template->setVariable('SELECTION_LIMIT_VALUE', $this->object->getSelectionLimit());
497  } else {
498  $template->setVariable('SELECTION_LIMIT_VALUE', 'null');
499  }
500  $template->setVariable("QUESTION_ID", $this->object->getId());
501  $questiontext = $this->object->getQuestionForHTMLOutput();
502  if ($show_inline_feedback && $this->hasInlineFeedback()) {
503  $questiontext .= $this->buildFocusAnchorHtml();
504  }
505  $template->setVariable("QUESTIONTEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($questiontext, true));
506  $questionoutput = $template->get();
507  if (!$show_question_only) {
508  // get page object output
509  $questionoutput = $this->getILIASPage($questionoutput);
510  }
511  return $questionoutput;
512  }
513 
514  public function getTestOutput(
515  int $active_id,
516  int $pass,
517  bool $is_question_postponed = false,
518  array|bool $user_post_solutions = false,
519  bool $show_specific_inline_feedback = false
520  ): string {
521  // shuffle output
522  $keys = $this->getChoiceKeys();
523 
524  // get the solution of the user for the active pass or from the last pass if allowed
525  $user_solution = [];
526  if ($active_id) {
527  $solutions = $this->object->getTestOutputSolutions($active_id, $pass);
528  // hey.
529  foreach ($solutions as $solution_value) {
530  // fau: testNav - don't add the dummy entry for 'none of the above' to the user options
531  if ($solution_value["value1"] == 'mc_none_above') {
532  $this->setUseEmptySolutionInputChecked(true);
533  continue;
534  }
535 
536  $user_solution[] = $solution_value["value1"];
537  // fau.
538  }
539 
540  if (empty($user_solution) && $this->object->getTestPresentationConfig()->isWorkedThrough()) {
541  $this->setUseEmptySolutionInputChecked(true);
542  }
543  }
544  // generate the question output
545  $this->tpl->addJavaScript('assets/js/ilAssMultipleChoice.js');
546  $this->tpl->addOnLoadCode('ilAssMultipleChoiceCharCounterInit();');
547 
548  $template = new ilTemplate("tpl.il_as_qpl_mc_mr_output.html", true, true, "components/ILIAS/TestQuestionPool");
549  foreach ($keys as $answer_id) {
550  $answer = $this->object->answers[$answer_id];
551  if ($answer->hasImage()) {
552  if ($this->object->getThumbSize()) {
553  $template->setCurrentBlock("preview");
554  $template->setVariable("URL_PREVIEW", $this->object->getImagePathWeb() . $answer->getImage());
555  $template->setVariable("TEXT_PREVIEW", $this->lng->txt('preview'));
556  $template->setVariable("IMG_PREVIEW", ilUtil::getImagePath('media/enlarge.svg'));
557  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $this->object->getThumbPrefix() . $answer->getImage());
558  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
559  $alt = $answer->getImage();
560  if (strlen($answer->getAnswertext())) {
561  $alt = $answer->getAnswertext();
562  }
563  $alt = preg_replace("/<[^>]*?>/", "", $alt);
564  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
565  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
566  $template->parseCurrentBlock();
567  } else {
568  $template->setCurrentBlock("answer_image");
569  $template->setVariable("ANSWER_IMAGE_URL", $this->object->getImagePathWeb() . $answer->getImage());
570  list($width, $height, $type, $attr) = getimagesize($this->object->getImagePath() . $answer->getImage());
571  $alt = $answer->getImage();
572  if (strlen($answer->getAnswertext())) {
573  $alt = $answer->getAnswertext();
574  }
575  $alt = preg_replace("/<[^>]*?>/", "", $alt);
576  $template->setVariable("ATTR", $attr);
577  $template->setVariable("ANSWER_IMAGE_ALT", ilLegacyFormElementsUtil::prepareFormOutput($alt));
578  $template->setVariable("ANSWER_IMAGE_TITLE", ilLegacyFormElementsUtil::prepareFormOutput($alt));
579  $template->parseCurrentBlock();
580  }
581  }
582 
583  if ($show_specific_inline_feedback) {
584  $this->populateSpecificFeedbackInline($user_solution, $answer_id, $template);
585  }
586 
587  $template->setCurrentBlock("answer_row");
588  $template->setVariable("QID", $this->object->getId());
589  $template->setVariable("ANSWER_ID", $answer_id);
590  $template->setVariable("ANSWER_TEXT", ilLegacyFormElementsUtil::prepareTextareaOutput($answer->getAnswertext(), true));
591  foreach ($user_solution as $mc_solution) {
592  if (strcmp($mc_solution, $answer_id) == 0) {
593  $template->setVariable("CHECKED_ANSWER", " checked=\"checked\"");
594  }
595  }
596  $template->parseCurrentBlock();
597  }
598 
599  $template->setVariable("QUESTIONTEXT", $this->object->getQuestionForHTMLOutput());
600  $template->setVariable("QUESTION_ID", $this->object->getId());
601  if ($this->object->getSelectionLimit()) {
602  $template->setVariable('SELECTION_LIMIT_HINT', sprintf(
603  $this->lng->txt('ass_mc_sel_lim_hint'),
604  $this->object->getSelectionLimit(),
605  $this->object->getAnswerCount()
606  ));
607 
608  $template->setVariable('SELECTION_LIMIT_VALUE', $this->object->getSelectionLimit());
609  } else {
610  $template->setVariable('SELECTION_LIMIT_VALUE', 'null');
611  }
612  $questionoutput = $template->get();
613  $pageoutput = $this->outQuestionPage("", $is_question_postponed, $active_id, $questionoutput, $show_specific_inline_feedback);
614  return $pageoutput;
615  }
616 
617  protected $useEmptySolutionInputChecked = false;
618 
619  public function isUseEmptySolutionInputChecked(): bool
620  {
622  }
623 
625  {
626  $this->useEmptySolutionInputChecked = $useEmptySolutionInputChecked;
627  }
628 
629  protected function getUseUnchangedAnswerCheckboxHtml(): string
630  {
631  // hey: prevPassSolutions - use abstracted template to share with other purposes of this kind
632  $this->tpl->addJavaScript('assets/js/ilAssMultipleChoice.js');
633 
634  $tpl = new ilTemplate('tpl.tst_question_additional_behaviour_checkbox.html', true, true, 'components/ILIAS/TestQuestionPool');
635 
636  // HEY: affects next if (!) /// noneAboveChecked repaired but disabled because the checked input ..
637  if (false) { // .. makes the qstEditController initialize the "edit" instead of the "answered" state
638  if ($this->isUseEmptySolutionInputChecked()) {
639  $tpl->setCurrentBlock('checked');
640  $tpl->touchBlock('checked');
642  }
643  }
644 
645  $tpl->setCurrentBlock('checkbox');
646  $tpl->setVariable('TXT_FORCE_FORM_DIFF_LABEL', $this->object->getTestPresentationConfig()->getUseUnchangedAnswerLabel());
648  // hey.
649  return $tpl->get();
650  }
651 
652  public function getPresentationJavascripts(): array
653  {
654  return ['assets/js/ilAssMultipleChoice.js'];
655  }
656 
662  public function getChoiceKeys(): array
663  {
664  $choice_keys = array_keys($this->object->answers);
665 
666  if ($this->object->getShuffle()) {
667  $choice_keys = $this->object->getShuffler()->transform($choice_keys);
668  }
669 
670  return $choice_keys;
671  }
672 
673  public function getSpecificFeedbackOutput(array $userSolution): string
674  {
675  // No return value, this question type supports inline specific feedback.
676  $output = "";
678  }
679 
681  {
682  $this->object->setShuffle($this->request_data_collector->bool('shuffle') ?? false);
683 
684  $selectionLimit = (int) $form->getItemByPostVar('selection_limit')?->getValue();
685  $this->object->setSelectionLimit($selectionLimit > 0 ? $selectionLimit : null);
686 
687  $feedback_setting = $this->request_data_collector->int('feedback_setting');
688  if ($feedback_setting !== 0) {
689  $this->object->setSpecificFeedbackSetting($feedback_setting);
690  }
691 
692  $types = $this->request_data_collector->int('types');
693  $this->object->setMultilineAnswerSetting($types);
694 
695  $choice = $this->request_data_collector->raw('choice');
696  if (isset($choice['imagename']) && is_array($choice['imagename']) && $types === 1) {
697  $this->object->setIsSingleline(true);
698  $this->tpl->setOnScreenMessage('info', $this->lng->txt('info_answer_type_change'), true);
699  } else {
700  $this->object->setIsSingleline($types === 0);
701  }
702 
703  $thumb_size = $this->request_data_collector->int('thumb_size') ?? $this->object->getThumbSize();
704  if ($thumb_size !== $this->object->getThumbSize()) {
705  $this->object->setThumbSize($thumb_size);
706  $this->rebuild_thumbnails = true;
707  }
708  }
709 
710  public function writeAnswerSpecificPostData(ilPropertyFormGUI $form): void
711  {
712  // Delete all existing answers and create new answers from the form data
713  $this->object->flushAnswers();
714 
715  $choice = $this->cleanupAnswerText(
716  $this->request_data_collector->raw('choice'),
717  !$this->object->isSingleline()
718  );
719 
720  if (!$this->object->isSingleline()) {
721  foreach ($choice['answer'] as $index => $answer) {
722  $answertext = $answer;
723  $this->object->addAnswer(
724  $answertext,
725  (float) str_replace(',', '.', $choice['points'][$index]),
726  (float) str_replace(',', '.', $choice['points_unchecked'][$index]),
727  $index,
728  null,
729  (int) $choice['answer_id'][$index]
730  );
731  }
732  return;
733  }
734 
735  foreach ($choice['answer'] as $index => $answertext) {
736  $answertext = htmlentities($answertext);
737  $picturefile = $choice['imagename'][$index] ?? null;
738  $file_org_name = $_FILES['choice']['name']['image'][$index] ?? '';
739  $file_temp_name = $_FILES['choice']['tmp_name']['image'][$index] ?? '';
740 
741  if ($file_temp_name !== '') {
742  // check suffix
743  $parts = explode('.', $file_org_name);
744  $suffix = strtolower(array_pop($parts));
745  if (in_array($suffix, ['jpg', 'jpeg', 'png', 'gif'])) {
746  // upload image
747  $filename = $this->object->buildHashedImageFilename($file_org_name);
748  if ($this->object->setImageFile($filename, $file_temp_name) === 0) {
749  $picturefile = $filename;
750  }
751  }
752  }
753 
754  $this->object->addAnswer(
755  $answertext,
756  (float) str_replace(',', '.', $choice['points'][$index]),
757  (float) str_replace(',', '.', $choice['points_unchecked'][$index]),
758  $index,
759  $picturefile,
760  $choice['answer_id'][$index]
761  );
762  }
763 
764  if ($this->rebuild_thumbnails) {
765  $this->object->setAnswers(
766  $this->object->rebuildThumbnails(
767  $this->object->isSingleline(),
768  $this->object->getThumbSize(),
769  $this->object->getImagePath(),
770  $this->object->getAnswers()
771  )
772  );
773  }
774  }
775 
776  public function populateQuestionSpecificFormPart(\ilPropertyFormGUI $form, bool $is_singleline = false): ilPropertyFormGUI
777  {
778  // shuffle
779  $shuffle = new ilCheckboxInputGUI($this->lng->txt("shuffle_answers"), "shuffle");
780  $shuffle->setValue(1);
781  $shuffle->setChecked($this->object->getShuffle());
782  $shuffle->setRequired(false);
783  $form->addItem($shuffle);
784 
785  $selLim = new ilNumberInputGUI($this->lng->txt('ass_mc_sel_lim_setting'), 'selection_limit');
786  $selLim->setInfo($this->lng->txt('ass_mc_sel_lim_setting_desc'));
787  $selLim->setSize(2);
788  $selLim->setRequired(false);
789  $selLim->allowDecimals(false);
790  $selLim->setMinvalueShouldBeGreater(false);
791  $selLim->setMaxvalueShouldBeLess(false);
792  $selLim->setMinValue(1);
793  $selLim->setMaxValue($this->object->getAnswerCount());
794  $selLim->setValue($this->object->getSelectionLimit());
795  $form->addItem($selLim);
796 
797  if (!$this->object->getSelfAssessmentEditingMode()) {
798  // Answer types
799  $types = new ilSelectInputGUI($this->lng->txt("answer_types"), "types");
800  $types->setRequired(false);
801  $types->setOptions([
802  0 => $this->lng->txt('answers_singleline'),
803  1 => $this->lng->txt('answers_multiline'),
804  ]);
805  $types->setValue($is_singleline ? 0 : 1);
806  $form->addItem($types);
807  }
808 
809  if ($is_singleline) {
810  // thumb size
811  $thumb_size = new ilNumberInputGUI($this->lng->txt('thumb_size'), "thumb_size");
812  $thumb_size->setSuffix($this->lng->txt('thumb_size_unit_pixel'));
813  $thumb_size->setMinValue($this->object->getMinimumThumbSize());
814  $thumb_size->setMaxValue($this->object->getMaximumThumbSize());
815  $thumb_size->setDecimals(0);
816  $thumb_size->setSize(6);
817  $thumb_size->setInfo($this->lng->txt('thumb_size_info'));
818  $thumb_size->setValue($this->object->getThumbSize());
819  $thumb_size->setRequired(true);
820  } else {
821  $thumb_size = new ilHiddenInputGUI('thumb_size');
822  $thumb_size->setValue($this->object->getThumbSize());
823  }
824  $form->addItem($thumb_size);
825 
826  return $form;
827  }
828 
829  public function populateAnswerSpecificFormPart(\ilPropertyFormGUI $form, bool $is_singleline = false): ilPropertyFormGUI
830  {
831  $choices = new ilMultipleChoiceWizardInputGUI($this->lng->txt("answers"), "choice");
832  $choices->setRequired(true);
833  $choices->setQuestionObject($this->object);
834  $choices->setSingleline($is_singleline);
835  $choices->setAllowMove(false);
836  if ($this->object->getSelfAssessmentEditingMode()) {
837  $choices->setSize(40);
838  }
839  if ($this->object->getAnswerCount() == 0) {
840  $this->object->addAnswer("", 0, 0, 0);
841  }
842  $choices->setValues(array_map(
843  function (ASS_AnswerMultipleResponseImage $value) {
844  $value->setAnswerText(html_entity_decode($value->getAnswerText()));
845  return $value;
846  },
847  $this->object->getAnswers()
848  ));
849  $form->addItem($choices);
850  return $form;
851  }
852 
863  {
864  return [];
865  }
866 
877  {
878  return [];
879  }
880 
881  private function aggregateAnswers($relevant_answers_chosen, $answers_defined_on_question): array
882  {
883  $aggregate = [];
884  foreach ($answers_defined_on_question as $answer) {
885  $aggregated_info_for_answer = [];
886  $aggregated_info_for_answer['answertext'] = $answer->getAnswerText();
887  $aggregated_info_for_answer['count_checked'] = 0;
888 
889  foreach ($relevant_answers_chosen as $relevant_answer) {
890  if ($relevant_answer['value1'] == $answer->getOrder()) {
891  $aggregated_info_for_answer['count_checked']++;
892  }
893  }
894  $aggregated_info_for_answer['count_unchecked'] =
895  ceil(count($relevant_answers_chosen) / count($answers_defined_on_question))
896  - $aggregated_info_for_answer['count_checked'];
897 
898  $aggregate[] = $aggregated_info_for_answer;
899  }
900  return $aggregate;
901  }
902 
903  private function populateSpecificFeedbackInline($user_solution, $answer_id, $template): void
904  {
905  if ($this->object->getSpecificFeedbackSetting() == 2) {
906  foreach ($user_solution as $mc_solution) {
907  if (strcmp($mc_solution, $answer_id) == 0) {
908  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id);
909  if (strlen($fb)) {
910  $template->setCurrentBlock("feedback");
911  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
912  $template->parseCurrentBlock();
913  }
914  }
915  }
916  }
917 
918  if ($this->object->getSpecificFeedbackSetting() == 1) {
919  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id);
920  if (strlen($fb)) {
921  $template->setCurrentBlock("feedback");
922  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
923  $template->parseCurrentBlock();
924  }
925  }
926 
927  if ($this->object->getSpecificFeedbackSetting() == 3) {
928  $answer = $this->object->getAnswer($answer_id);
929 
930  if ($answer->getPoints() > 0) {
931  $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0, $answer_id);
932  if (strlen($fb)) {
933  $template->setCurrentBlock("feedback");
934  $template->setVariable("FEEDBACK", ilLegacyFormElementsUtil::prepareTextareaOutput($fb, true));
935  $template->parseCurrentBlock();
936  }
937  }
938  }
939  }
940 
941  // fau: testNav - new functions setWithNoneAbove() and setIsAnswered()
942  // moved functionality to ilTestQuestionPresentationConfig
943  // fau.
944 
948  protected function buildEditForm(bool $is_singleline = true): ilPropertyFormGUI
949  {
950  $form = new ilPropertyFormGUI();
951  $form->setFormAction($this->ctrl->getFormAction($this));
952  $form->setTitle($this->outQuestionType());
953  $form->setTableWidth("100%");
954  $form->setId("assmultiplechoice");
955 
956  $this->addBasicQuestionFormProperties($form);
957  $this->populateQuestionSpecificFormPart($form, $is_singleline);
958  $this->populateAnswerSpecificFormPart($form, $is_singleline);
959  $this->populateTaxonomyFormSection($form);
960  $this->addQuestionFormCommandButtons($form);
961  return $form;
962  }
963 
964  public function getAnswersFrequency($relevantAnswers, $questionIndex): array
965  {
966  $agg = $this->aggregateAnswers($relevantAnswers, $this->object->getAnswers());
967 
968  $answers = [];
969 
970  foreach ($agg as $ans) {
971  $answers[] = [
972  'answer' => $ans['answertext'],
973  'frequency' => $ans['count_checked']
974  ];
975  }
976 
977  return $answers;
978  }
979 
981  {
982  $choices = new ilAssMultipleChoiceCorrectionsInputGUI($this->lng->txt("answers"), "choice");
983  $choices->setRequired(true);
984  $choices->setQuestionObject($this->object);
985  $choices->setValues($this->object->getAnswers());
986  $form->addItem($choices);
987  }
988 
993  {
994  $input = $form->getItemByPostVar('choice');
995  $answerElements = $input->getValues();
996 
997  foreach ($this->object->getAnswers() as $index => $answer) {
998  /* @var ASS_AnswerMultipleResponseImage $answer */
999  $answer->setPointsChecked((float) str_replace(',', '.', $answerElements[$index]->getPointsChecked()));
1000  $answer->setPointsUnchecked((float) str_replace(',', '.', $answerElements[$index]->getPointsUnchecked()));
1001  }
1002  }
1003 }
hasCorrectSolution($activeId, $passIndex)
setSuffix(string $a_value)
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:61
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.
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)
ilGlobalPageTemplate $tpl
static prepareFormOutput($a_str, bool $a_strip=false)
Class for multiple choice tests.
populateTaxonomyFormSection(ilPropertyFormGUI $form)
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
This class represents a hidden form property in a property form.
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
setFormAction(string $a_formaction)
This class represents a number property in a property form.
writeAnswerSpecificPostData(ilPropertyFormGUI $form)
Extracts the answer specific values from the request and applies them to the data object...
static getImagePath(string $image_name, string $module_path="", string $mode="output", bool $offline=false)
get image path (for images located in a template directory)
static getHtmlPath(string $relative_path)
get url of path
writeQuestionSpecificPostData(ilPropertyFormGUI $form)
Extracts the question specific values from the request and applies them to the data object...
setRequired(bool $a_required)
aggregateAnswers($relevant_answers_chosen, $answers_defined_on_question)
setTableWidth(string $a_width)
editQuestion(bool $checkonly=false, ?bool $is_save_cmd=null)
$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
__construct(Container $dic, ilPlugin $plugin)
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.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getPreview(bool $show_question_only=false, bool $show_inline_feedback=false)
buildEditForm(bool $is_singleline=true)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getTestOutput(int $active_id, int $pass, bool $is_question_postponed=false, array|bool $user_post_solutions=false, bool $show_specific_inline_feedback=false)
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.
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,)
renderEditForm(ilPropertyFormGUI $form)
getGenericFeedbackOutput(int $active_id, ?int $pass)
getSolutionOutput(int $active_id, ?int $pass=null, 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_inline_feedback=true)