ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilAssClozeTestFeedback.php
Go to the documentation of this file.
1<?php
2
19use 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;
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 = [];
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
168 {
169 foreach ($this->questionOBJ->getGaps() as $gapIndex => $gap) {
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
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) {
211 $this->buildTextGapGivenAnswerFeedbackLabel($gapIndex, $item),
212 true
213 );
214
215 $propertyPostVar = "feedback_answer_{$gapIndex}_{$answerIndex}";
216
218 $propertyLabel,
219 $propertyPostVar,
220 $this->questionOBJ->isAdditionalContentEditingModePageObject()
221 ));
222 }
223
225 $this->buildTextGapWrongAnswerFeedbackLabel($gapIndex),
226 true
227 );
228
229 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_TEXT_GAP_NOMATCH_INDEX;
230
232 $propertyLabel,
233 $propertyPostVar,
234 $this->questionOBJ->isAdditionalContentEditingModePageObject()
235 ));
236
238 $this->buildTextGapEmptyFeedbackLabel($gapIndex),
239 true
240 );
241
242 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_TEXT_GAP_EMPTY_INDEX;
243
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) {
255 $this->buildSelectGapOptionFeedbackLabel($gapIndex, $item),
256 true
257 );
258
259 $propertyPostVar = "feedback_answer_{$gapIndex}_{$optIndex}";
260
262 $propertyLabel,
263 $propertyPostVar,
264 $this->questionOBJ->isAdditionalContentEditingModePageObject()
265 ));
266 }
267
269 $this->buildSelectGapEmptyFeedbackLabel($gapIndex),
270 true
271 );
272
273 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_SELECT_GAP_EMPTY_INDEX;
274
276 $propertyLabel,
277 $propertyPostVar,
278 $this->questionOBJ->isAdditionalContentEditingModePageObject()
279 ));
280 }
281
287 protected function completeFbPropsForNumericGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex): void
288 {
290 $this->buildNumericGapValueHitFeedbackLabel($gapIndex),
291 true
292 );
293
294 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_VALUE_HIT_INDEX;
295
297 $propertyLabel,
298 $propertyPostVar,
299 $this->questionOBJ->isAdditionalContentEditingModePageObject()
300 ));
301
302 if ($gap->numericRangeExists()) {
304 $this->buildNumericGapRangeHitFeedbackLabel($gapIndex),
305 true
306 );
307
308 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_RANGE_HIT_INDEX;
309
311 $propertyLabel,
312 $propertyPostVar,
313 $this->questionOBJ->isAdditionalContentEditingModePageObject()
314 ));
315 }
316
318 $this->buildNumericGapTooLowFeedbackLabel($gapIndex),
319 true
320 );
321
322 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_TOO_LOW_INDEX;
323
325 $propertyLabel,
326 $propertyPostVar,
327 $this->questionOBJ->isAdditionalContentEditingModePageObject()
328 ));
329
331 $this->buildNumericGapTooHighFeedbackLabel($gapIndex),
332 true
333 );
334
335 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_TOO_HIGH_INDEX;
336
338 $propertyLabel,
339 $propertyPostVar,
340 $this->questionOBJ->isAdditionalContentEditingModePageObject()
341 ));
342
344 $this->buildNumericGapEmptyFeedbackLabel($gapIndex),
345 true
346 );
347
348 $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_NUMERIC_GAP_EMPTY_INDEX;
349
351 $propertyLabel,
352 $propertyPostVar,
353 $this->questionOBJ->isAdditionalContentEditingModePageObject()
354 ));
355 }
356
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()) {
367 } else {
368 switch ($this->questionOBJ->getFeedbackMode()) {
370
372 break;
373
375
377 break;
378 }
379 }
380 }
381 }
382
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
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
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()) {
481
483 break;
484
486
488 break;
489 }
490 }
491 }
492
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
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
636 int $source_question_id,
637 int $target_question_id
638 ): void {
639 $res = $this->db->queryF(
640 "SELECT feedback_mode FROM {$this->questionOBJ->getAdditionalTableName()} WHERE question_fi = %s",
641 ['integer'],
642 [$source_question_id]
643 );
644
645 $row = $this->db->fetchAssoc($res);
646
647 $this->db->update(
648 $this->questionOBJ->getAdditionalTableName(),
649 [ 'feedback_mode' => ['text', $row['feedback_mode']] ],
650 [ 'question_fi' => ['integer', $target_question_id] ]
651 );
652 }
653
654 protected function cloneSpecificFeedback(int $originalQuestionId, int $duplicateQuestionId): void
655 {
656 $this->cloneSpecificFeedbackSetting($originalQuestionId, $duplicateQuestionId);
657 parent::cloneSpecificFeedback($originalQuestionId, $duplicateQuestionId);
658 }
659
664 protected function saveSpecificFeedbackMode(int $questionId, string $feedbackMode): void
665 {
666 $this->questionOBJ->setFeedbackMode($feedbackMode);
667
668 $this->db->update(
669 $this->questionOBJ->getAdditionalTableName(),
670 ['feedback_mode' => ['text', $feedbackMode]],
671 ['question_fi' => ['integer', $questionId]]
672 );
673 }
674
675 protected function buildPostVarForFbFieldPerGapQuestion(int $gapIndex): string
676 {
677 return "feedback_answer_{$gapIndex}";
678 }
679
680 protected function buildPostVarForFbFieldPerGapAnswers(int $gapIndex, int $answerIndex): string
681 {
682 return "feedback_answer_{$gapIndex}_{$answerIndex}";
683 }
684
685 protected function getSpecificAnswerFeedbackFormValue(int $gapIndex, int $answerIndex): string
686 {
687 if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
688 $pageObjectId = $this->getSpecificAnswerFeedbackPageObjectId(
689 $this->questionOBJ->getId(),
690 $gapIndex,
691 $answerIndex
692 );
693
694 $value = $this->getPageObjectNonEditableValueHTML(
695 $this->getSpecificAnswerFeedbackPageObjectType(),
696 $pageObjectId
697 );
698 } else {
700 $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), $gapIndex, $answerIndex)
701 );
702 }
703
704 return $value;
705 }
706
707 protected function cleanupSpecificAnswerFeedbacks(string $fbMode): void
708 {
709 switch ($fbMode) {
710 case self::FB_MODE_GAP_QUESTION:
711 $feedbackIds = $this->fetchFeedbackIdsForGapQuestionMode();
712 break;
713
714 case self::FB_MODE_GAP_ANSWERS:
715 $feedbackIds = $this->fetchFeedbackIdsForGapAnswersMode();
716 break;
717
718 default: $feedbackIds = [];
719 }
720
721 $this->deleteSpecificAnswerFeedbacksByIds($feedbackIds);
722 }
723
727 protected function fetchFeedbackIdsForGapQuestionMode(): array
728 {
729 $feedbackIdentifiers = new ilAssSpecificFeedbackIdentifierList();
730 $feedbackIdentifiers->load($this->questionOBJ->getId());
731
732 $feedbackIds = [];
733
734 foreach ($feedbackIdentifiers as $identifier) {
735 if ($identifier->getAnswerIndex() != self::SINGLE_GAP_FB_ANSWER_INDEX) {
736 continue;
737 }
738
739 $feedbackIds[] = $identifier->getFeedbackId();
740 }
741
742 return $feedbackIds;
743 }
744
748 protected function fetchFeedbackIdsForGapAnswersMode(): array
749 {
750 $feedbackIdentifiers = new ilAssSpecificFeedbackIdentifierList();
751 $feedbackIdentifiers->load($this->questionOBJ->getId());
752
753 $feedbackIds = [];
754
755 foreach ($feedbackIdentifiers as $identifier) {
756 if ($identifier->getAnswerIndex() == self::SINGLE_GAP_FB_ANSWER_INDEX) {
757 continue;
758 }
759
760 $feedbackIds[] = $identifier->getFeedbackId();
761 }
762
763 return $feedbackIds;
764 }
765
766 public function isSpecificAnswerFeedbackAvailable(int $question_id): bool
767 {
768 if ($this->questionOBJ->getFeedbackMode() === self::FB_MODE_GAP_QUESTION) {
769 $feedback_ids = $this->fetchFeedbackIdsForGapQuestionMode();
770 } else {
771 $feedback_ids = $this->fetchFeedbackIdsForGapAnswersMode();
772 }
773
774 if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
775 $all_feedback_content = '';
776 foreach ($feedback_ids as $feedback_id) {
777 $all_feedback_content .= $this->getPageObjectXML(
778 $this->getSpecificAnswerFeedbackPageObjectType(),
779 $feedback_id
780 );
781 }
782 return trim(strip_tags($all_feedback_content)) !== '';
783 }
784
785 return implode('', $this->getSpecificFeedbackContentForFeedbackIds($feedback_ids)) !== '';
786 }
787
791 protected function deleteSpecificAnswerFeedbacksByIds(array $feedbackIds): void
792 {
793 if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) {
794 foreach ($feedbackIds as $fbId) {
795 $this->ensurePageObjectDeleted($this->getSpecificAnswerFeedbackPageObjectType(), $fbId);
796 }
797 }
798
799 $IN_feedbackIds = $this->db->in('feedback_id', $feedbackIds, false, 'integer');
800 $this->db->manipulate("DELETE FROM {$this->getSpecificFeedbackTableName()} WHERE {$IN_feedbackIds}");
801 }
802
803 public function determineTestOutputGapFeedback(int $gap_index, int $answer_index): string
804 {
805 if ($this->questionOBJ->getFeedbackMode() === self::FB_MODE_GAP_QUESTION) {
806 return $this->getSpecificAnswerFeedbackTestPresentation(
807 $this->questionOBJ->getId(),
808 $gap_index,
809 self::SINGLE_GAP_FB_ANSWER_INDEX
810 );
811 }
812
813 return $this->getSpecificAnswerFeedbackTestPresentation($this->questionOBJ->getId(), $gap_index, $answer_index);
814 }
815
816 public function determineAnswerIndexForAnswerValue(assClozeGap $gap, string $answerValue): int
817 {
818 switch ($gap->getType()) {
820 if ($answerValue === '') {
821 return self::FB_TEXT_GAP_EMPTY_INDEX;
822 }
823
824 $items = $gap->getItems($this->randomGroup()->dontShuffle());
825
826 foreach ($items as $answerIndex => $answer) {
827 /* @var assAnswerCloze $answer */
828
829 if ($answer->getAnswertext() == $answerValue) {
830 return $answerIndex;
831 }
832 }
833
834 return self::FB_TEXT_GAP_NOMATCH_INDEX;
835
837 if ($answerValue !== '') {
838 return $answerValue;
839 }
840
841 return self::FB_SELECT_GAP_EMPTY_INDEX;
842
843 default:
845 if ($answerValue === '') {
846 return self::FB_NUMERIC_GAP_EMPTY_INDEX;
847 }
848
849 /* @var assAnswerCloze $item */
850
851 $item = current($gap->getItems($this->randomGroup()->dontShuffle()));
852
853 if ($answerValue == $item->getAnswertext()) {
854 return self::FB_NUMERIC_GAP_VALUE_HIT_INDEX;
855 }
856
857 $math = new EvalMath();
858
859 $item = $gap->getItem(0);
860 $lowerBound = $math->evaluate($item->getLowerBound());
861 $upperBound = $math->evaluate($item->getUpperBound());
862 $preciseValue = $math->evaluate($item->getAnswertext());
863
864 $solutionValue = $math->evaluate($answerValue);
865
866 if ($solutionValue == $preciseValue) {
867 return self::FB_NUMERIC_GAP_VALUE_HIT_INDEX;
868 } elseif ($solutionValue >= $lowerBound && $solutionValue <= $upperBound) {
869 return self::FB_NUMERIC_GAP_RANGE_HIT_INDEX;
870 } elseif ($solutionValue < $lowerBound) {
871 return self::FB_NUMERIC_GAP_TOO_LOW_INDEX;
872 }
873
874 // if ($solutionValue > $upperBound) {
875 return self::FB_NUMERIC_GAP_TOO_HIGH_INDEX;
876 //}
877 }
878 }
879
880 private function randomGroup(): RandomGroup
881 {
882 global $DIC;
883
884 return $DIC->refinery()->random();
885 }
886}
getAnswertext()
Gets the answer text.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class for cloze question gaps.
getItem($a_index)
Gets the item with a given index.
getItems(Transformation $shuffler, ?int $gap_index=null)
completeFbPropsForNumericGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex)
const FB_MODE_GAP_QUESTION
constants for different feedback modes (per gap or per gap-answers/options)
completeFormPropsForFeedbackModeGapQuestion(ilRadioOption $fbModeOpt)
completeFormPropsForFeedbackModeGapAnswers(ilRadioOption $fbModeOpt)
determineTestOutputGapFeedback(int $gap_index, int $answer_index)
buildTextGapGivenAnswerFeedbackLabel(int $gapIndex, assAnswerCloze $item)
completeFbPropsForTextGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex)
saveFbPropsForNumericGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
isSpecificAnswerFeedbackAvailable(int $question_id)
cloneSpecificFeedback(int $originalQuestionId, int $duplicateQuestionId)
duplicates the SPECIFIC feedback relating to the given original question id and saves it for the give...
cloneSpecificFeedbackSetting(int $source_question_id, int $target_question_id)
initFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
saveFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
saveFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
initFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex)
determineAnswerIndexForAnswerValue(assClozeGap $gap, string $answerValue)
const FB_TEXT_GAP_EMPTY_INDEX
constants for answer indexes in case of FB_MODE_GAP_ANSWERS
initFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form)
deleteSpecificAnswerFeedbacksByIds(array $feedbackIds)
isSaveableInPageObjectEditingMode()
returns the fact wether the feedback editing form is saveable in page object editing or not.
initSpecificFormProperties(ilPropertyFormGUI $form)
initialises a given form object's specific form properties relating to this question type
getSpecificAnswerFeedbackFormValue(int $gapIndex, int $answerIndex)
saveFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form)
saveSpecificFeedbackMode(int $questionId, string $feedbackMode)
saves the given specific feedback mode for the given question id to the db.
saveSpecificFormProperties(ilPropertyFormGUI $form)
saves a given form object's SPECIFIC form properties relating to this question type
completeSpecificFormProperties(ilPropertyFormGUI $form)
completes a given form object with the specific form properties required by this question type
initFbPropsForNumericGap(ilPropertyFormGUI $form, int $gapIndex, assClozeGap $gap)
initFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form)
buildGapFeedbackLabel(int $gapIndex, assClozeGap $gap)
builds an answer option label from given (mixed type) index and answer (overwrites parent method from...
buildPostVarForFbFieldPerGapAnswers(int $gapIndex, int $answerIndex)
buildSelectGapOptionFeedbackLabel(int $gapIndex, assAnswerCloze $item)
completeFbPropsForSelectGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex)
saveFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form)
buildTextGapWrongAnswerFeedbackLabel(int $gapIndex)
saveSpecificAnswerFeedbackContent(int $question_id, int $question_index, int $answer_index, string $feedback_content)
buildFeedbackContentFormProperty(string $label, string $post_var, bool $as_non_editable)
builds and returns a form property gui object with the given label and postvar that is addable to pro...
This class represents a section header in a property form.
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,...
static prepareFormOutput($a_str, bool $a_strip=false)
This class represents a property form user interface.
getItemByPostVar(string $a_post_var)
This class represents a property in a property form.
This class represents an option in a radio group.
$res
Definition: ltiservices.php:69
global $DIC
Definition: shib_login.php:26