ILIAS  release_8 Revision v8.23
class.ilAssClozeTestFeedback.php
Go to the documentation of this file.
1 <?php
2 
19 use ILIAS\Refinery\Random\Group as RandomGroup;
20 
30 {
34  public const FB_MODE_GAP_QUESTION = 'gapQuestion';
35  public const FB_MODE_GAP_ANSWERS = 'gapAnswers';
36 
40  public const FB_TEXT_GAP_EMPTY_INDEX = -1;
41  public const FB_TEXT_GAP_NOMATCH_INDEX = -2; // indexes for preset answers: 0 - n
42  public const FB_SELECT_GAP_EMPTY_INDEX = -1; // indexes for given select options: 0 - n
43  public const FB_NUMERIC_GAP_EMPTY_INDEX = -1;
46  public const FB_NUMERIC_GAP_TOO_LOW_INDEX = 2;
48 
49  public const SINGLE_GAP_FB_ANSWER_INDEX = -10;
50 
51  public function isSaveableInPageObjectEditingMode(): bool
52  {
53  return true;
54  }
55 
60  protected function buildGapFeedbackLabel(int $gapIndex, assClozeGap $gap): string
61  {
62  $answers = array();
63 
64  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $item) {
65  $answers[] = '"' . ilLegacyFormElementsUtil::prepareFormOutput($item->getAnswertext()) . '"';
66  }
67 
68  $answers = implode(' / ', $answers);
69 
70  return sprintf(
71  $this->lng->txt('ass_cloze_gap_fb_gap_label'),
72  $gapIndex + 1,
73  $answers
74  );
75  }
76 
77  protected function buildTextGapGivenAnswerFeedbackLabel(int $gapIndex, assAnswerCloze $item): string
78  {
79  return sprintf(
80  $this->lng->txt('ass_cloze_gap_fb_txt_match_label'),
81  $gapIndex + 1,
83  );
84  }
85 
86  protected function buildTextGapWrongAnswerFeedbackLabel(int $gapIndex): string
87  {
88  return sprintf($this->lng->txt('ass_cloze_gap_fb_txt_nomatch_label'), $gapIndex + 1);
89  }
90 
91  protected function buildTextGapEmptyFeedbackLabel(int $gapIndex): string
92  {
93  return sprintf($this->lng->txt('ass_cloze_gap_fb_txt_empty_label'), $gapIndex + 1);
94  }
95 
96  protected function buildSelectGapOptionFeedbackLabel(int $gapIndex, assAnswerCloze $item): string
97  {
98  return sprintf(
99  $this->lng->txt('ass_cloze_gap_fb_sel_opt_label'),
100  $gapIndex + 1,
102  );
103  }
104 
105  protected function buildSelectGapEmptyFeedbackLabel(int $gapIndex): string
106  {
107  return sprintf($this->lng->txt('ass_cloze_gap_fb_sel_empty_label'), $gapIndex + 1);
108  }
109 
110  protected function buildNumericGapValueHitFeedbackLabel(int $gapIndex): string
111  {
112  return sprintf($this->lng->txt('ass_cloze_gap_fb_num_valuehit_label'), $gapIndex + 1);
113  }
114 
115  protected function buildNumericGapRangeHitFeedbackLabel(int $gapIndex): string
116  {
117  return sprintf($this->lng->txt('ass_cloze_gap_fb_num_rangehit_label'), $gapIndex + 1);
118  }
119 
120  protected function buildNumericGapTooLowFeedbackLabel(int $gapIndex): string
121  {
122  return sprintf($this->lng->txt('ass_cloze_gap_fb_num_toolow_label'), $gapIndex + 1);
123  }
124 
125  protected function buildNumericGapTooHighFeedbackLabel(int $gapIndex): string
126  {
127  return sprintf($this->lng->txt('ass_cloze_gap_fb_num_toohigh_label'), $gapIndex + 1);
128  }
129 
130  protected function buildNumericGapEmptyFeedbackLabel(int $gapIndex): string
131  {
132  return sprintf($this->lng->txt('ass_cloze_gap_fb_num_empty_label'), $gapIndex + 1);
133  }
134 
136  {
137  if (!$this->questionOBJ->getSelfAssessmentEditingMode()) {
138  $header = new ilFormSectionHeaderGUI();
139  $header->setTitle($this->lng->txt('feedback_answers'));
140  $form->addItem($header);
141 
142  $feedbackMode = new ilRadioGroupInputGUI(
143  $this->lng->txt('ass_cloze_fb_mode'),
144  'feedback_mode'
145  );
146  $feedbackMode->setRequired(true);
147  $form->addItem($feedbackMode);
148 
149  $fbModeGapQuestion = new ilRadioOption(
150  $this->lng->txt('ass_cloze_fb_mode_gap_qst'),
151  self::FB_MODE_GAP_QUESTION,
152  $this->lng->txt('ass_cloze_fb_mode_gap_qst_info')
153  );
154  $this->completeFormPropsForFeedbackModeGapQuestion($fbModeGapQuestion);
155  $feedbackMode->addOption($fbModeGapQuestion);
156 
157  $fbModeGapAnswers = new ilRadioOption(
158  $this->lng->txt('ass_cloze_fb_mode_gap_answ'),
159  self::FB_MODE_GAP_ANSWERS,
160  $this->lng->txt('ass_cloze_fb_mode_gap_answ_info')
161  );
162  $this->completeFormPropsForFeedbackModeGapAnswers($fbModeGapAnswers);
163  $feedbackMode->addOption($fbModeGapAnswers);
164  }
165  }
166 
167  protected function completeFormPropsForFeedbackModeGapQuestion(ilRadioOption $fbModeOpt): void
168  {
169  foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
170  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
171  $this->buildGapFeedbackLabel($gapIndex, $gap),
172  true
173  );
174 
175  $fbModeOpt->addSubItem(
177  $propertyLabel,
178  $this->buildPostVarForFbFieldPerGapQuestion($gapIndex),
179  $this->questionOBJ->isAdditionalContentEditingModePageObject()
180  )
181  );
182  }
183  }
184 
185  protected function completeFormPropsForFeedbackModeGapAnswers(ilRadioOption $fbModeOpt): void
186  {
187  foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
188  switch ($gap->getType()) {
190 
191  $this->completeFbPropsForTextGap($fbModeOpt, $gap, $gapIndex);
192  break;
193 
195 
196  $this->completeFbPropsForSelectGap($fbModeOpt, $gap, $gapIndex);
197  break;
198 
200 
201  $this->completeFbPropsForNumericGap($fbModeOpt, $gap, $gapIndex);
202  break;
203  }
204  }
205  }
206 
207  protected function completeFbPropsForTextGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex): void
208  {
209  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $answerIndex => $item) {
210  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
211  $this->buildTextGapGivenAnswerFeedbackLabel($gapIndex, $item),
212  true
213  );
214 
215  $propertyPostVar = "feedback_answer_{$gapIndex}_{$answerIndex}";
216 
217  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
218  $propertyLabel,
219  $propertyPostVar,
220  $this->questionOBJ->isAdditionalContentEditingModePageObject()
221  ));
222  }
223 
224  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
225  $this->buildTextGapWrongAnswerFeedbackLabel($gapIndex),
226  true
227  );
228 
229  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_TEXT_GAP_NOMATCH_INDEX;
230 
231  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
232  $propertyLabel,
233  $propertyPostVar,
234  $this->questionOBJ->isAdditionalContentEditingModePageObject()
235  ));
236 
237  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
238  $this->buildTextGapEmptyFeedbackLabel($gapIndex),
239  true
240  );
241 
242  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_TEXT_GAP_EMPTY_INDEX;
243 
244  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
245  $propertyLabel,
246  $propertyPostVar,
247  $this->questionOBJ->isAdditionalContentEditingModePageObject()
248  ));
249  }
250 
251  protected function completeFbPropsForSelectGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex): void
252  {
253  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $optIndex => $item) {
254  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
255  $this->buildSelectGapOptionFeedbackLabel($gapIndex, $item),
256  true
257  );
258 
259  $propertyPostVar = "feedback_answer_{$gapIndex}_{$optIndex}";
260 
261  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
262  $propertyLabel,
263  $propertyPostVar,
264  $this->questionOBJ->isAdditionalContentEditingModePageObject()
265  ));
266  }
267 
268  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
269  $this->buildSelectGapEmptyFeedbackLabel($gapIndex),
270  true
271  );
272 
273  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_SELECT_GAP_EMPTY_INDEX;
274 
275  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
276  $propertyLabel,
277  $propertyPostVar,
278  $this->questionOBJ->isAdditionalContentEditingModePageObject()
279  ));
280  }
281 
287  protected function completeFbPropsForNumericGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex): void
288  {
289  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
290  $this->buildNumericGapValueHitFeedbackLabel($gapIndex),
291  true
292  );
293 
294  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_VALUE_HIT_INDEX;
295 
296  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
297  $propertyLabel,
298  $propertyPostVar,
299  $this->questionOBJ->isAdditionalContentEditingModePageObject()
300  ));
301 
302  if ($gap->numericRangeExists()) {
303  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
304  $this->buildNumericGapRangeHitFeedbackLabel($gapIndex),
305  true
306  );
307 
308  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_RANGE_HIT_INDEX;
309 
310  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
311  $propertyLabel,
312  $propertyPostVar,
313  $this->questionOBJ->isAdditionalContentEditingModePageObject()
314  ));
315  }
316 
317  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
318  $this->buildNumericGapTooLowFeedbackLabel($gapIndex),
319  true
320  );
321 
322  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_TOO_LOW_INDEX;
323 
324  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
325  $propertyLabel,
326  $propertyPostVar,
327  $this->questionOBJ->isAdditionalContentEditingModePageObject()
328  ));
329 
330  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
331  $this->buildNumericGapTooHighFeedbackLabel($gapIndex),
332  true
333  );
334 
335  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_TOO_HIGH_INDEX;
336 
337  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
338  $propertyLabel,
339  $propertyPostVar,
340  $this->questionOBJ->isAdditionalContentEditingModePageObject()
341  ));
342 
343  $propertyLabel = $this->questionOBJ->prepareTextareaOutput(
344  $this->buildNumericGapEmptyFeedbackLabel($gapIndex),
345  true
346  );
347 
348  $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_EMPTY_INDEX;
349 
350  $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty(
351  $propertyLabel,
352  $propertyPostVar,
353  $this->questionOBJ->isAdditionalContentEditingModePageObject()
354  ));
355  }
356 
357  public function initSpecificFormProperties(ilPropertyFormGUI $form): void
358  {
359  if (!$this->questionOBJ->getSelfAssessmentEditingMode()) {
360  /* @var ilRadioGroupInputGUI $fbMode */
361  $fbMode = $form->getItemByPostVar('feedback_mode');
362  $fbMode->setValue($this->questionOBJ->getFeedbackMode());
363 
364  if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
365  $this->initFeedbackFieldsPerGapQuestion($form);
366  $this->initFeedbackFieldsPerGapAnswers($form);
367  } else {
368  switch ($this->questionOBJ->getFeedbackMode()) {
369  case self::FB_MODE_GAP_QUESTION:
370 
371  $this->initFeedbackFieldsPerGapQuestion($form);
372  break;
373 
374  case self::FB_MODE_GAP_ANSWERS:
375 
376  $this->initFeedbackFieldsPerGapAnswers($form);
377  break;
378  }
379  }
380  }
381  }
382 
383  protected function initFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form): void
384  {
385  foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
386  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::SINGLE_GAP_FB_ANSWER_INDEX);
387  $form->getItemByPostVar($this->buildPostVarForFbFieldPerGapQuestion($gapIndex))->setValue($value);
388  }
389  }
390 
391  protected function initFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form): void
392  {
393  foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
394  switch ($gap->getType()) {
396 
397  $this->initFbPropsForTextGap($form, $gap, $gapIndex);
398  break;
399 
401 
402  $this->initFbPropsForSelectGap($form, $gap, $gapIndex);
403  break;
404 
406 
407  $this->initFbPropsForNumericGap($form, $gapIndex, $gap);
408  break;
409  }
410  }
411  }
412 
413  protected function initFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex): void
414  {
415  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $answerIndex => $item) {
416  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, $answerIndex);
417  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex);
418  $form->getItemByPostVar($postVar)->setValue($value);
419  }
420 
421  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX);
422  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX);
423  $form->getItemByPostVar($postVar)->setValue($value);
424 
425  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX);
426  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX);
427  $form->getItemByPostVar($postVar)->setValue($value);
428  }
429 
430  protected function initFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex): void
431  {
432  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $optIndex => $item) {
433  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, $optIndex);
434  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $optIndex);
435  $form->getItemByPostVar($postVar)->setValue($value);
436  }
437 
438  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX);
439  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX);
440  $form->getItemByPostVar($postVar)->setValue($value);
441  }
442 
443  protected function initFbPropsForNumericGap(ilPropertyFormGUI $form, int $gapIndex, assClozeGap $gap): void
444  {
445  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX);
446  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX);
447  $form->getItemByPostVar($postVar)->setValue($value);
448 
449  if ($gap->numericRangeExists()) {
450  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX);
451  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX);
452  $form->getItemByPostVar($postVar)->setValue($value);
453  }
454 
455  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX);
456  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX);
457  $form->getItemByPostVar($postVar)->setValue($value);
458 
459  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX);
460  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX);
461  $form->getItemByPostVar($postVar)->setValue($value);
462 
463  $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX);
464  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX);
465  $form->getItemByPostVar($postVar)->setValue($value);
466  }
467 
468  public function saveSpecificFormProperties(ilPropertyFormGUI $form): void
469  {
470  if (!$this->questionOBJ->getSelfAssessmentEditingMode()) {
471  $fbMode = $form->getItemByPostVar('feedback_mode')->getValue();
472 
473  if ($fbMode != $this->questionOBJ->getFeedbackMode()) {
474  $this->cleanupSpecificAnswerFeedbacks($this->questionOBJ->getFeedbackMode());
475  }
476 
477  $this->saveSpecificFeedbackMode($this->questionOBJ->getId(), $fbMode);
478 
479  switch ($this->questionOBJ->getFeedbackMode()) {
480  case self::FB_MODE_GAP_QUESTION:
481 
482  $this->saveFeedbackFieldsPerGapQuestion($form);
483  break;
484 
485  case self::FB_MODE_GAP_ANSWERS:
486 
487  $this->saveFeedbackFieldsPerGapAnswers($form);
488  break;
489  }
490  }
491  }
492 
493  protected function saveFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form): void
494  {
495  foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
496  $postVar = $this->buildPostVarForFbFieldPerGapQuestion($gapIndex);
497  $value = $form->getItemByPostVar($postVar)->getValue();
498 
500  $this->questionOBJ->getId(),
501  $gapIndex,
502  self::SINGLE_GAP_FB_ANSWER_INDEX,
503  $value
504  );
505  }
506  }
507 
508  protected function saveFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form): void
509  {
510  foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
511  switch ($gap->getType()) {
513 
514  $this->saveFbPropsForTextGap($form, $gap, $gapIndex);
515  break;
516 
518 
519  $this->saveFbPropsForSelectGap($form, $gap, $gapIndex);
520  break;
521 
523 
524  $this->saveFbPropsForNumericGap($form, $gap, $gapIndex);
525  break;
526  }
527  }
528  }
529 
530  protected function saveFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex): void
531  {
532  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $answerIndex => $item) {
533  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex);
534  $value = $form->getItemByPostVar($postVar)->getValue();
536  $this->questionOBJ->getId(),
537  $gapIndex,
538  $answerIndex,
539  $value
540  );
541  }
542 
543  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX);
544  $value = $form->getItemByPostVar($postVar)->getValue();
546  $this->questionOBJ->getId(),
547  $gapIndex,
548  self::FB_TEXT_GAP_NOMATCH_INDEX,
549  $value
550  );
551 
552  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX);
553  $value = $form->getItemByPostVar($postVar)->getValue();
555  $this->questionOBJ->getId(),
556  $gapIndex,
557  self::FB_TEXT_GAP_EMPTY_INDEX,
558  $value
559  );
560  }
561 
562  protected function saveFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex): void
563  {
564  foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $optIndex => $item) {
565  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $optIndex);
566  $value = $form->getItemByPostVar($postVar)->getValue();
568  $this->questionOBJ->getId(),
569  $gapIndex,
570  $optIndex,
571  $value
572  );
573  }
574 
575  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX);
576  $value = $form->getItemByPostVar($postVar)->getValue();
578  $this->questionOBJ->getId(),
579  $gapIndex,
580  self::FB_SELECT_GAP_EMPTY_INDEX,
581  $value
582  );
583  }
584 
585  protected function saveFbPropsForNumericGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex): void
586  {
587  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX);
588  $value = $form->getItemByPostVar($postVar)->getValue();
590  $this->questionOBJ->getId(),
591  $gapIndex,
592  self::FB_NUMERIC_GAP_VALUE_HIT_INDEX,
593  $value
594  );
595 
596  if ($gap->numericRangeExists()) {
597  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX);
598  $value = $form->getItemByPostVar($postVar)->getValue();
600  $this->questionOBJ->getId(),
601  $gapIndex,
602  self::FB_NUMERIC_GAP_RANGE_HIT_INDEX,
603  $value
604  );
605  }
606 
607  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX);
608  $value = $form->getItemByPostVar($postVar)->getValue();
610  $this->questionOBJ->getId(),
611  $gapIndex,
612  self::FB_NUMERIC_GAP_TOO_LOW_INDEX,
613  $value
614  );
615 
616  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX);
617  $value = $form->getItemByPostVar($postVar)->getValue();
619  $this->questionOBJ->getId(),
620  $gapIndex,
621  self::FB_NUMERIC_GAP_TOO_HIGH_INDEX,
622  $value
623  );
624 
625  $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX);
626  $value = $form->getItemByPostVar($postVar)->getValue();
628  $this->questionOBJ->getId(),
629  $gapIndex,
630  self::FB_NUMERIC_GAP_EMPTY_INDEX,
631  $value
632  );
633  }
634 
635  protected function duplicateSpecificFeedback(int $originalQuestionId, int $duplicateQuestionId): void
636  {
637  $this->syncSpecificFeedbackSetting($originalQuestionId, $duplicateQuestionId);
638 
639  parent::duplicateSpecificFeedback($originalQuestionId, $duplicateQuestionId);
640  }
641 
642  private function syncSpecificFeedbackSetting(int $sourceQuestionId, int $targetQuestionId): void
643  {
644  $res = $this->db->queryF(
645  "SELECT feedback_mode FROM {$this->questionOBJ->getAdditionalTableName()} WHERE question_fi = %s",
646  array('integer'),
647  array($sourceQuestionId)
648  );
649 
650  $row = $this->db->fetchAssoc($res);
651 
652  $this->db->update(
653  $this->questionOBJ->getAdditionalTableName(),
654  array( 'feedback_mode' => array('text', $row['feedback_mode']) ),
655  array( 'question_fi' => array('integer', $targetQuestionId) )
656  );
657  }
658 
659  protected function syncSpecificFeedback(int $originalQuestionId, int $duplicateQuestionId): void
660  {
661  $this->syncSpecificFeedbackSetting($originalQuestionId, $duplicateQuestionId);
662  parent::syncSpecificFeedback($originalQuestionId, $duplicateQuestionId);
663  }
664 
669  protected function saveSpecificFeedbackMode(int $questionId, string $feedbackMode): void
670  {
671  $this->questionOBJ->setFeedbackMode($feedbackMode);
672 
673  $this->db->update(
674  $this->questionOBJ->getAdditionalTableName(),
675  array('feedback_mode' => array('text', $feedbackMode)),
676  array('question_fi' => array('integer', $questionId))
677  );
678  }
679 
680  protected function buildPostVarForFbFieldPerGapQuestion(int $gapIndex): string
681  {
682  return "feedback_answer_{$gapIndex}";
683  }
684 
685  protected function buildPostVarForFbFieldPerGapAnswers(int $gapIndex, int $answerIndex): string
686  {
687  return "feedback_answer_{$gapIndex}_{$answerIndex}";
688  }
689 
690  protected function getSpecificAnswerFeedbackFormValue(int $gapIndex, int $answerIndex): string
691  {
692  if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
693  $pageObjectId = $this->getSpecificAnswerFeedbackPageObjectId(
694  $this->questionOBJ->getId(),
695  $gapIndex,
696  $answerIndex
697  );
698 
699  $value = $this->getPageObjectNonEditableValueHTML(
701  $pageObjectId
702  );
703  } else {
704  $value = $this->questionOBJ->prepareTextareaOutput(
705  $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), $gapIndex, $answerIndex)
706  );
707  }
708 
709  return $value;
710  }
711 
712  protected function cleanupSpecificAnswerFeedbacks(string $fbMode): void
713  {
714  switch ($fbMode) {
715  case self::FB_MODE_GAP_QUESTION:
716  $feedbackIds = $this->fetchFeedbackIdsForGapQuestionMode();
717  break;
718 
719  case self::FB_MODE_GAP_ANSWERS:
720  $feedbackIds = $this->fetchFeedbackIdsForGapAnswersMode();
721  break;
722 
723  default: $feedbackIds = array();
724  }
725 
726  $this->deleteSpecificAnswerFeedbacksByIds($feedbackIds);
727  }
728 
732  protected function fetchFeedbackIdsForGapQuestionMode(): array
733  {
734  require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php';
735  $feedbackIdentifiers = new ilAssSpecificFeedbackIdentifierList();
736  $feedbackIdentifiers->load($this->questionOBJ->getId());
737 
738  $feedbackIds = array();
739 
740  foreach ($feedbackIdentifiers as $identifier) {
741  if ($identifier->getAnswerIndex() != self::SINGLE_GAP_FB_ANSWER_INDEX) {
742  continue;
743  }
744 
745  $feedbackIds[] = $identifier->getFeedbackId();
746  }
747 
748  return $feedbackIds;
749  }
750 
754  protected function fetchFeedbackIdsForGapAnswersMode(): array
755  {
756  require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php';
757  $feedbackIdentifiers = new ilAssSpecificFeedbackIdentifierList();
758  $feedbackIdentifiers->load($this->questionOBJ->getId());
759 
760  $feedbackIds = array();
761 
762  foreach ($feedbackIdentifiers as $identifier) {
763  if ($identifier->getAnswerIndex() == self::SINGLE_GAP_FB_ANSWER_INDEX) {
764  continue;
765  }
766 
767  $feedbackIds[] = $identifier->getFeedbackId();
768  }
769 
770  return $feedbackIds;
771  }
772 
773  public function isSpecificAnswerFeedbackAvailable(int $question_id): bool
774  {
775  if ($this->questionOBJ->getFeedbackMode() === self::FB_MODE_GAP_QUESTION) {
776  $feedback_ids = $this->fetchFeedbackIdsForGapQuestionMode();
777  } else {
778  $feedback_ids = $this->fetchFeedbackIdsForGapAnswersMode();
779  }
780 
781  if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
782  $all_feedback_content = '';
783  foreach ($feedback_ids as $feedback_id) {
784  $all_feedback_content .= $this->getPageObjectXML(
786  $feedback_id
787  );
788  }
789  return trim(strip_tags($all_feedback_content)) !== '';
790  }
791 
792  return implode('', $this->getSpecificFeedbackContentForFeedbackIds($feedback_ids)) !== '';
793  }
794 
798  protected function deleteSpecificAnswerFeedbacksByIds(array $feedbackIds): void
799  {
800  if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
801  foreach ($feedbackIds as $fbId) {
803  }
804  }
805 
806  $IN_feedbackIds = $this->db->in('feedback_id', $feedbackIds, false, 'integer');
807  $this->db->manipulate("DELETE FROM {$this->getSpecificFeedbackTableName()} WHERE {$IN_feedbackIds}");
808  }
809 
810  public function determineTestOutputGapFeedback(int $gapIndex, int $answerIndex): string
811  {
812  if ($this->questionOBJ->getFeedbackMode() == self::FB_MODE_GAP_QUESTION) {
814  $this->questionOBJ->getId(),
815  $gapIndex,
816  self::SINGLE_GAP_FB_ANSWER_INDEX
817  );
818  }
819 
820  return $this->getSpecificAnswerFeedbackTestPresentation($this->questionOBJ->getId(), $gapIndex, $answerIndex);
821  }
822 
823  public function determineAnswerIndexForAnswerValue(assClozeGap $gap, string $answerValue): int
824  {
825  switch ($gap->getType()) {
826  case CLOZE_TEXT:
827 
828  if (!strlen($answerValue)) {
829  return self::FB_TEXT_GAP_EMPTY_INDEX;
830  }
831 
832  $items = $gap->getItems($this->randomGroup()->dontShuffle());
833 
834  foreach ($items as $answerIndex => $answer) {
835  /* @var assAnswerCloze $answer */
836 
837  if ($answer->getAnswertext() == $answerValue) {
838  return $answerIndex;
839  }
840  }
841 
842  return self::FB_TEXT_GAP_NOMATCH_INDEX;
843 
844  case CLOZE_SELECT:
845 
846  if (strlen($answerValue)) {
847  return $answerValue;
848  }
849 
850  return self::FB_SELECT_GAP_EMPTY_INDEX;
851 
852  default:
853  case CLOZE_NUMERIC:
854 
855  if (!strlen($answerValue)) {
856  return self::FB_NUMERIC_GAP_EMPTY_INDEX;
857  }
858 
859  /* @var assAnswerCloze $item */
860 
861  $item = current($gap->getItems($this->randomGroup()->dontShuffle()));
862 
863  if ($answerValue == $item->getAnswertext()) {
864  return self::FB_NUMERIC_GAP_VALUE_HIT_INDEX;
865  }
866 
867  require_once 'Services/Math/classes/class.EvalMath.php';
868  $math = new EvalMath();
869 
870  $item = $gap->getItem(0);
871  $lowerBound = $math->evaluate($item->getLowerBound());
872  $upperBound = $math->evaluate($item->getUpperBound());
873  $preciseValue = $math->evaluate($item->getAnswertext());
874 
875  $solutionValue = $math->evaluate($answerValue);
876 
877  if ($solutionValue == $preciseValue) {
878  return self::FB_NUMERIC_GAP_VALUE_HIT_INDEX;
879  } elseif ($solutionValue >= $lowerBound && $solutionValue <= $upperBound) {
880  return self::FB_NUMERIC_GAP_RANGE_HIT_INDEX;
881  } elseif ($solutionValue < $lowerBound) {
882  return self::FB_NUMERIC_GAP_TOO_LOW_INDEX;
883  }
884 
885  // if ($solutionValue > $upperBound) {
886  return self::FB_NUMERIC_GAP_TOO_HIGH_INDEX;
887  //}
888  }
889  }
890 
891  private function randomGroup(): RandomGroup
892  {
893  global $DIC;
894 
895  return $DIC->refinery()->random();
896  }
897 }
getSpecificAnswerFeedbackContent(int $questionId, int $questionIndex, int $answerIndex)
deleteSpecificAnswerFeedbacksByIds(array $feedbackIds)
saveFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$res
Definition: ltiservices.php:69
buildGapFeedbackLabel(int $gapIndex, assClozeGap $gap)
builds an answer option label from given (mixed type) index and answer (overwrites parent method from...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
initFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
initFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form)
ensurePageObjectDeleted(string $pageObjectType, int $pageObjectId)
syncSpecificFeedbackSetting(int $sourceQuestionId, int $targetQuestionId)
initFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form)
completeFormPropsForFeedbackModeGapQuestion(ilRadioOption $fbModeOpt)
getItemByPostVar(string $a_post_var)
const CLOZE_TEXT
Cloze question constants.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
initFbPropsForNumericGap(ilPropertyFormGUI $form, int $gapIndex, assClozeGap $gap)
saveSpecificFeedbackMode(int $questionId, string $feedbackMode)
saves the given specific feedback mode for the given question id to the db.
completeFbPropsForSelectGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex)
buildTextGapWrongAnswerFeedbackLabel(int $gapIndex)
completeFormPropsForFeedbackModeGapAnswers(ilRadioOption $fbModeOpt)
isSpecificAnswerFeedbackAvailable(int $question_id)
completeFbPropsForNumericGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex)
getSpecificAnswerFeedbackPageObjectId(int $questionId, int $questionIndex, int $answerIndex)
returns a useable page object id for specific answer feedback page objects for the given question id ...
Class for cloze question gaps.
static prepareFormOutput($a_str, bool $a_strip=false)
getSpecificAnswerFeedbackTestPresentation(int $questionId, int $questionIndex, int $answerIndex)
returns the html of SPECIFIC feedback for the given question id and answer index for test presentatio...
saveSpecificFormProperties(ilPropertyFormGUI $form)
initSpecificFormProperties(ilPropertyFormGUI $form)
syncSpecificFeedback(int $originalQuestionId, int $duplicateQuestionId)
global $DIC
Definition: feed.php:28
determineTestOutputGapFeedback(int $gapIndex, int $answerIndex)
getItems(Transformation $shuffler, ?int $gap_index=null)
Gets the items of a cloze gap.
const CLOZE_SELECT
This class represents a property in a property form.
saveFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
completeSpecificFormProperties(ilPropertyFormGUI $form)
determineAnswerIndexForAnswerValue(assClozeGap $gap, string $answerValue)
buildSelectGapOptionFeedbackLabel(int $gapIndex, assAnswerCloze $item)
completeFbPropsForTextGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex)
getPageObjectXML(string $pageObjectType, int $pageObjectId)
buildTextGapGivenAnswerFeedbackLabel(int $gapIndex, assAnswerCloze $item)
buildFeedbackContentFormProperty(string $label, string $postVar, bool $asNonEditable)
builds and returns a form property gui object with the given label and postvar that is addable to pro...
getPageObjectNonEditableValueHTML(string $pageObjectType, int $pageObjectId)
returns html content to be used as value for non editable value form properties in feedback editing f...
initFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
setRequired(bool $a_required)
getSpecificAnswerFeedbackFormValue(int $gapIndex, int $answerIndex)
saveFbPropsForNumericGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
const FB_TEXT_GAP_EMPTY_INDEX
constants for answer indexes in case of FB_MODE_GAP_ANSWERS
duplicateSpecificFeedback(int $originalQuestionId, int $duplicateQuestionId)
getAnswertext()
Gets the answer text.
saveFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form)
const CLOZE_NUMERIC
getItem($a_index)
Gets the item with a given index.
saveFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form)
const FB_MODE_GAP_QUESTION
constants for different feedback modes (per gap or per gap-answers/options)
buildPostVarForFbFieldPerGapAnswers(int $gapIndex, int $answerIndex)
saveSpecificAnswerFeedbackContent(int $questionId, int $questionIndex, int $answerIndex, string $feedbackContent)