ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.assClozeTestGUI.php
Go to the documentation of this file.
1<?php
2
19use ILIAS\UI\Factory as UIFactory;
20use ILIAS\UI\Renderer as UIRenderer;
21use ILIAS\Refinery\Random\Group as RandomGroup;
23
40{
41 public const JS_INSERT_GAP_CODE_AT_CARET = <<<JS
42 jQuery.fn.extend({
43 insertGapCodeAtCaret: function() {
44 return this.each(function(i) {
45 var code_start = '[gap]';
46 var code_end = '[/gap]';
47 if (typeof tinyMCE !== 'undefined' && typeof tinyMCE.get('cloze_text') !== 'undefined') {
48 var ed = tinyMCE.get('cloze_text');
49 il.ClozeHelper.internetExplorerTinyMCECursorFix(ed);
50 ed.selection.setContent(code_start + ed.selection.getContent() + code_end);
51 ed.focus();
52 return;
53 }
54 if (document.selection) {
55 //For browsers like Internet Explorer
56 this.focus();
57 sel = document.selection.createRange();
58 sel.text = code_start + sel.text + code_end;
59 this.focus();
60 } else if (this.selectionStart || this.selectionStart == '0') {
61 //For browsers like Firefox and Webkit based
62 var startPos = this.selectionStart;
63 var endPos = this.selectionEnd;
64 var scrollTop = this.scrollTop;
65 this.value = this.value.substring(0, startPos)
66 + code_start
67 + this.value.substring(startPos, endPos)
68 + code_end
69 + this.value.substring(endPos, this.value.length);
70 this.focus();
71 this.scrollTop = scrollTop;
72 } else {
73 this.value += code_start + code_end;
74 this.focus();
75 }
76 });
77 }
78 });
79JS;
80
84 private $gapIndex;
85
86 private RandomGroup $randomGroup;
87 private UIFactory $ui_factory;
88 private UIRenderer $ui_renderer;
90
96 public function __construct(int $id = -1)
97 {
99 global $DIC;
100 $this->ui_factory = $DIC['ui.factory'];
101 $this->ui_renderer = $DIC['ui.renderer'];
102 $this->db = $DIC['ilDB'];
103
104 $this->object = new assClozeTest();
105 if ($id >= 0) {
106 $this->object->loadFromDb($id);
107 }
108
109 $this->randomGroup = $this->refinery->random();
110 }
111
112 public function getCommand($cmd)
113 {
114 if (preg_match("/^(removegap|addgap)_(\d+)$/", $cmd, $matches)) {
115 $cmd = $matches[1];
116 $this->gapIndex = $matches[2];
117 }
118 return $cmd;
119 }
120
124 protected function writePostData(bool $always = false): int
125 {
126 $hasErrors = (!$always) ? $this->editQuestion(true) : false;
127 if ($hasErrors) {
128 return 1;
129 }
130
131 $cloze_text = $this->removeIndizesFromGapText(
132 $this->request_data_collector->raw('cloze_text')
133 );
134
135 $this->object->setQuestion(
136 $this->request_data_collector->raw('question')
137 );
138
140 $this->object->setClozeText($cloze_text);
141 $this->object->setTextgapRating($this->request_data_collector->raw('textgap_rating'));
142 $this->object->setIdenticalScoring($this->request_data_collector->bool('identical_scoring') ?? false);
143 $this->object->setFixedTextLength($this->request_data_collector->int('fixedTextLength') ?? 0);
146 return 0;
147 }
148
150 {
151 $gaps = $this->request_data_collector->strArray('gap');
152 if (empty($gaps)) {
153 return;
154 }
155
156 if ($this->ctrl->getCmd() !== 'createGaps') {
157 $this->object->clearGapAnswers();
158 }
159
160 $answer_trafo = $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->string());
161 $points_trafo = $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->float());
162
163 foreach (array_keys($gaps) as $idx) {
164 $cloze_type = $this->request_data_collector->int('clozetype_' . $idx);
165 $this->object->setGapType($idx, $cloze_type);
166
167 $gap_idx = $this->request_data_collector->raw('gap_' . $idx);
168
169 if ($cloze_type === assClozeGap::TYPE_TEXT || $cloze_type === assClozeGap::TYPE_SELECT) {
170 $answer = $answer_trafo->transform($gap_idx['answer'] ?? []);
171 if ($this->ctrl->getCmd() !== 'createGaps') {
172 if (!empty($answer)) {
173 foreach ($answer as $order => $value) {
174 $this->object->addGapAnswer($idx, $order, $value);
175 }
176 } else {
177 $this->object->addGapAnswer($idx, 0, '');
178 }
179 }
180
181 $points = $points_trafo->transform($gap_idx['points'] ?? []);
182 foreach ($points as $order => $value) {
183 $this->object->setGapAnswerPoints($idx, $order, $value);
184 }
185 }
186
187 if ($cloze_type === assClozeGap::TYPE_TEXT || $cloze_type === assClozeGap::TYPE_NUMERIC) {
188 $this->object->setGapShuffle(0);
189
190 $gap_size = $this->request_data_collector->int('gap_' . $idx . '_gapsize');
191 if ($gap_size !== 0) {
192 $this->object->setGapSize($idx, $gap_size);
193 }
194 } else {
195 $this->object->setGapShuffle($idx, $this->request_data_collector->int('shuffle_' . $idx));
196 }
197
198 if ($cloze_type === assClozeGap::TYPE_NUMERIC && $this->object->getGap($idx) !== null) {
199 $this->object->getGap($idx)->clearItems();
200
201 $gap_idx_numeric = $this->request_data_collector->float('gap_' . $idx . '_numeric');
202 if ($gap_idx_numeric !== 0.0) {
203 $this->object->addGapAnswer($idx, 0, (string) $gap_idx_numeric);
204 $this->object->setGapAnswerLowerBound(
205 $idx,
206 0,
207 (string) $this->request_data_collector->float('gap_' . $idx . '_numeric_lower')
208 );
209 $this->object->setGapAnswerUpperBound(
210 $idx,
211 0,
212 (string) $this->request_data_collector->float('gap_' . $idx . '_numeric_upper')
213 );
214 $this->object->setGapAnswerPoints(
215 $idx,
216 0,
217 $this->request_data_collector->float('gap_' . $idx . '_numeric_points')
218 );
219 } else {
220 if ($this->ctrl->getCmd() !== 'createGaps') {
221 $this->object->addGapAnswer($idx, 0, '');
222 }
223
224 $this->object->setGapAnswerLowerBound($idx, 0, '');
225 $this->object->setGapAnswerUpperBound($idx, 0, '');
226 }
227 }
228
229 $ass_cloze_gab_combination = new assClozeGapCombination($this->db);
230 $ass_cloze_gab_combination->clearGapCombinationsFromDb($this->object->getId());
231
232 $gap_combination = $this->request_data_collector->rawArray('gap_combination', 4);
233 if ($gap_combination !== []) {
234 $ass_cloze_gab_combination->saveGapCombinationToDb(
235 $this->object->getId(),
236 $gap_combination,
237 $this->request_data_collector->rawArray('gap_combination_values')
238 );
239 $this->object->setGapCombinationsExists(true);
240 }
241 }
242
243 if ($this->ctrl->getCmd() !== 'createGaps') {
244 $this->object->updateClozeTextFromGaps();
245 }
246 }
247
249 {
250 $this->object->setClozeText($this->request_data_collector->string('cloze_quest'));
251 $this->object->setTextgapRating($this->request_data_collector->string('textgap_rating'));
252 $this->object->setIdenticalScoring($this->request_data_collector->bool('textgap_rating') ?? true);
253 $this->object->setFixedTextLength($this->request_data_collector->int('fixedTextLength'));
254 }
255
261 public function editQuestion(
262 bool $checkonly = false,
263 ?bool $is_save_cmd = null
264 ): bool {
265 $save = $is_save_cmd ?? $this->isSaveCommand();
266
267 $form = new ilPropertyFormGUI();
268 $this->editForm = $form;
269
270 $form->setFormAction($this->ctrl->getFormAction($this));
271 $form->setTitle($this->outQuestionType());
272 $form->setMultipart(false);
273 $form->setTableWidth("100%");
274 $form->setId("assclozetest");
275
276 $this->addBasicQuestionFormProperties($form);
278 $this->populateAnswerSpecificFormPart($form);
279 $this->populateTaxonomyFormSection($form);
280
281 $this->addQuestionFormCommandButtons($form);
282
283 $errors = false;
284
285 if ($save) {
286 $form->setValuesByPost();
287 $errors = !$form->checkInput();
288 $form->setValuesByPost();
289
290 $gap_combinations = $this->request_data_collector->raw('gap_combination');
291 if (is_array($gap_combinations)
292 && $gap_combinations !== []
293 && $this->hasErrorInGapCombinationPoints($gap_combinations)) {
294 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('points_non_numeric_or_negative_msg'));
295 $errors = true;
296 }
297
298 if ($errors) {
299 $checkonly = false;
300 }
301 }
302
303 if (!$checkonly) {
304 $this->renderEditForm($form);
305 }
306 return $errors;
307 }
308
309 private function hasErrorInGapCombinationPoints(array $gap_combinations): bool
310 {
311 foreach ($gap_combinations['points'] as $gaps_points) {
312 foreach ($gaps_points as $points) {
313 $points_standardized = str_replace(',', '.', $points);
314 if (!is_numeric($points_standardized)
315 || (float) $points_standardized < 0) {
316 return true;
317 }
318 }
319 }
320
321 return false;
322 }
323
325 {
326 // cloze text
327 $cloze_text = new ilTextAreaInputGUI($this->lng->txt("cloze_text"), 'cloze_text');
328 $cloze_text->setRequired(true);
329 $cloze_text->setValue($this->applyIndizesToGapText($this->object->getClozeText()));
330 $cloze_text->setInfo($this->lng->txt("close_text_hint") . ' '
331 . $this->lng->txt('latex_edit_info'));
332 $cloze_text->setRows(10);
333 $cloze_text->setCols(80);
334 if (!$this->object->getSelfAssessmentEditingMode()) {
335 if ($this->object->getAdditionalContentEditingMode() !== assQuestion::ADDITIONAL_CONTENT_EDITING_MODE_IPE) {
336 $cloze_text->setUseRte(true);
337 $cloze_text->setRteTags(ilRTESettings::_getUsedHTMLTags("assessment"));
338 }
339 } else {
341 $cloze_text->setUseTagsForRteOnly(false);
342 }
343 $cloze_text->setRTESupport($this->object->getId(), "qpl", "assessment");
344 $form->addItem($cloze_text);
345
346 $tpl = new ilTemplate("tpl.il_as_qpl_cloze_gap_button_code.html", true, true, "components/ILIAS/TestQuestionPool");
347
348 $button = new ilCustomInputGUI('&nbsp;', '');
349
350 $button_text_gap = $this->ui_factory->button()->standard($this->lng->txt('text_gap'), '')
351 ->withAdditionalOnLoadCode(
352 $this->getAddGapButtonClickClosure('text')
353 );
354 $button_select_gap = $this->ui_factory->button()->standard($this->lng->txt('select_gap'), '')
355 ->withAdditionalOnLoadCode(
356 $this->getAddGapButtonClickClosure('select')
357 );
358 $button_numeric_gap = $this->ui_factory->button()->standard($this->lng->txt('numeric_gap'), '')
359 ->withAdditionalOnLoadCode(
360 $this->getAddGapButtonClickClosure('numeric')
361 );
362
363 $tpl->setVariable('BUTTON', $this->ui_renderer->render([
364 $button_text_gap,
365 $button_select_gap,
366 $button_numeric_gap
367 ]));
368 $tpl->parseCurrentBlock();
369
370 $button->setHtml($tpl->get());
371 $this->tpl->addOnloadCode(self::JS_INSERT_GAP_CODE_AT_CARET);
372 $form->addItem($button);
373
374 // text rating
375 if (!$this->object->getSelfAssessmentEditingMode()) {
376 $textrating = new ilSelectInputGUI($this->lng->txt("text_rating"), "textgap_rating");
377 $text_options = [
378 "ci" => $this->lng->txt("cloze_textgap_case_insensitive"),
379 "cs" => $this->lng->txt("cloze_textgap_case_sensitive"),
380 "l1" => sprintf($this->lng->txt("cloze_textgap_levenshtein_of"), "1"),
381 "l2" => sprintf($this->lng->txt("cloze_textgap_levenshtein_of"), "2"),
382 "l3" => sprintf($this->lng->txt("cloze_textgap_levenshtein_of"), "3"),
383 "l4" => sprintf($this->lng->txt("cloze_textgap_levenshtein_of"), "4"),
384 "l5" => sprintf($this->lng->txt("cloze_textgap_levenshtein_of"), "5")
385 ];
386 $textrating->setOptions($text_options);
387 $textrating->setValue($this->object->getTextgapRating());
388 $form->addItem($textrating);
389
390 // text field length
391 $fixedTextLength = new ilNumberInputGUI($this->lng->txt("cloze_fixed_textlength"), "fixedTextLength");
392 $ftl = $this->object->getFixedTextLength();
393
394 $fixedTextLength->setValue($ftl > 0 ? $ftl : '');
395 $fixedTextLength->setMinValue(0);
396 $fixedTextLength->setSize(3);
397 $fixedTextLength->setMaxLength(6);
398 $fixedTextLength->setInfo($this->lng->txt('cloze_fixed_textlength_description'));
399 $fixedTextLength->setRequired(false);
400 $form->addItem($fixedTextLength);
401
402 // identical scoring
403 $identical_scoring = new ilCheckboxInputGUI($this->lng->txt("identical_scoring"), "identical_scoring");
404 $identical_scoring->setValue(1);
405 $identical_scoring->setChecked($this->object->getIdenticalScoring());
406 $identical_scoring->setInfo($this->lng->txt('identical_scoring_desc'));
407 $identical_scoring->setRequired(false);
408 $form->addItem($identical_scoring);
409 }
410 return $form;
411 }
412
413 private function getAddGapButtonClickClosure(string $gap_type): Closure
414 {
415 return fn($id) => "var el = document.getElementById('{$id}').addEventListener('click', "
416 . '(e) => {'
417 . ' e.preventDefault();'
418 . " ClozeQuestionGapBuilder.addGapClickFunction('{$gap_type}');"
419 . '});';
420 }
421
423 {
424 $json = $this->populateJSON();
425 $assClozeGapCombinationObject = new assClozeGapCombination($this->db);
426 $combination_exists = $assClozeGapCombinationObject->combinationExistsForQid($this->object->getId());
427 $combinations = [];
428 if ($combination_exists) {
429 $combinations = $assClozeGapCombinationObject->loadFromDb($this->object->getId());
430 }
431 $new_builder = new ilClozeGapInputBuilderGUI();
432 $header = new ilFormSectionHeaderGUI();
433 $form->addItem($header);
434 $new_builder->setValueByArray($json);
435 $new_builder->setValueCombinationFromDb($combinations);
436 $form->addItem($new_builder);
437 return $form;
438 }
439
440 protected function populateJSON(): array
441 {
442 $gap = $this->object->getGaps();
443 $array = [];
444 if ($gap == null) {
445 return $array;
446 }
447 $translate_type = ['text','select','numeric'];
448 $i = 0;
449 foreach ($gap as $content) {
450 $shuffle = false;
451 $value = $content->getItemsRaw();
452 $items = [];
453 for ($j = 0, $jMax = count($value); $j < $jMax; $j++) {
454 if ($content->isNumericGap()) {
455 $items[$j] = [
456 'answer' => $value[$j]->getAnswerText(),
457 'lower' => $value[$j]->getLowerBound(),
458 'upper' => $value[$j]->getUpperBound(),
459 'points' => $value[$j]->getPoints(),
460 'error' => false
461 ];
462 } else {
463 $items[$j] = [
464 'answer' => $this->escapeTemplatePlaceholders($value[$j]->getAnswerText()),
465 'points' => $value[$j]->getPoints(),
466 'error' => false
467 ];
468
469 if ($content->isSelectGap()) {
470 $shuffle = $content->getShuffle();
471 }
472 }
473 }
474 $answers[$i] = [
475 'type' => $translate_type[$content->getType()] ,
476 'values' => $items ,
477 'shuffle' => $shuffle,
478 'text_field_length' => $content->getGapSize() > 0 ? $content->getGapSize() : '',
479 'used_in_gap_combination' => true];
480 $i++;
481 }
482 return $answers;
483 }
495 protected function populateGapFormPart($form, $gapCounter): ilPropertyFormGUI
496 {
497 $gap = $this->object->getGap($gapCounter);
498
499 if ($gap == null) {
500 return $form;
501 }
502
503 $header = new ilFormSectionHeaderGUI();
504 $header->setTitle($this->lng->txt("gap") . " " . ($gapCounter + 1));
505 $form->addItem($header);
506
507 $gapcounter = new ilHiddenInputGUI("gap[$gapCounter]");
508 $gapcounter->setValue($gapCounter);
509 $form->addItem($gapcounter);
510
511 $gaptype = new ilSelectInputGUI($this->lng->txt('type'), "clozetype_$gapCounter");
512 $options = [
513 0 => $this->lng->txt("text_gap"),
514 1 => $this->lng->txt("select_gap"),
515 2 => $this->lng->txt("numeric_gap")
516 ];
517 $gaptype->setOptions($options);
518 $gaptype->setValue($gap->getType());
519 $form->addItem($gaptype);
520
521 if ($gap->getType() == assClozeGap::TYPE_TEXT) {
522 $this->populateGapSizeFormPart($form, $gap, $gapCounter);
523
524 if (count($gap->getItemsRaw()) == 0) {
525 $gap->addItem(new assAnswerCloze("", 0, 0));
526 }
527 $this->populateTextGapFormPart($form, $gap, $gapCounter);
528 } elseif ($gap->getType() == assClozeGap::TYPE_SELECT) {
529 if (count($gap->getItemsRaw()) == 0) {
530 $gap->addItem(new assAnswerCloze("", 0, 0));
531 }
532 $this->populateSelectGapFormPart($form, $gap, $gapCounter);
533 } elseif ($gap->getType() == assClozeGap::TYPE_NUMERIC) {
534 $this->populateGapSizeFormPart($form, $gap, $gapCounter);
535
536 if (count($gap->getItemsRaw()) == 0) {
537 $gap->addItem(new assAnswerCloze("", 0, 0));
538 }
539 foreach ($gap->getItemsRaw() as $item) {
540 $this->populateNumericGapFormPart($form, $item, $gapCounter);
541 }
542 }
543 return $form;
544 }
545
551 protected function populateGapSizeFormPart($form, $gap, $gapCounter): ilPropertyFormGUI
552 {
553 $gapSizeFormItem = new ilNumberInputGUI($this->lng->txt('cloze_fixed_textlength'), "gap_" . $gapCounter . '_gapsize');
554
555 $gapSizeFormItem->allowDecimals(false);
556 $gapSizeFormItem->setMinValue(0);
557 $gapSizeFormItem->setSize(3);
558 $gapSizeFormItem->setMaxLength(6);
559 $gapSizeFormItem->setInfo($this->lng->txt('cloze_gap_size_info'));
560 $gapSizeFormItem->setValue($gap->getGapSize());
561 $form->addItem($gapSizeFormItem);
562
563 return $form;
564 }
565
578 protected function populateSelectGapFormPart($form, $gap, $gapCounter): ilPropertyFormGUI
579 {
580 $values = new ilAnswerWizardInputGUI($this->lng->txt("values"), "gap_" . $gapCounter . "");
581 $values->setRequired(true);
582 $values->setQuestionObject($this->object);
583 $values->setSingleline(true);
584 $values->setAllowMove(false);
585
586 $values->setValues($gap->getItemsRaw());
587 $form->addItem($values);
588
589 // shuffle
590 $shuffle = new ilCheckboxInputGUI($this->lng->txt("shuffle_answers"), "shuffle_" . $gapCounter . "");
591 $shuffle->setValue(1);
592 $shuffle->setChecked($gap->getShuffle());
593 $shuffle->setRequired(false);
594 $form->addItem($shuffle);
595 return $form;
596 }
597
609 protected function populateTextGapFormPart($form, $gap, $gapCounter): ilPropertyFormGUI
610 {
611 $values = new ilAnswerWizardInputGUI($this->lng->txt("values"), "gap_" . $gapCounter . "");
612 $values->setRequired(true);
613 $values->setQuestionObject($this->object);
614 $values->setSingleline(true);
615 $values->setAllowMove(false);
616 $values->setValues($gap->getItemsRaw());
617 $form->addItem($values);
618
619 if ($this->object->getFixedTextLength() > 0) {
620 $values->setSize($this->object->getFixedTextLength());
621 $values->setMaxLength($this->object->getFixedTextLength());
622 }
623
624 return $form;
625 }
626
638 protected function populateNumericGapFormPart($form, $gap, $gapCounter): ilPropertyFormGUI
639 {
640 // #8944: the js-based ouput in self-assessment cannot support formulas
641 if (!$this->object->getSelfAssessmentEditingMode()) {
642 $value = new ilFormulaInputGUI($this->lng->txt('value'), "gap_" . $gapCounter . "_numeric");
643 $value->setInlineStyle('text-align: right;');
644
645 $lowerbound = new ilFormulaInputGUI($this->lng->txt('range_lower_limit'), "gap_" . $gapCounter . "_numeric_lower");
646 $lowerbound->setInlineStyle('text-align: right;');
647
648 $upperbound = new ilFormulaInputGUI($this->lng->txt('range_upper_limit'), "gap_" . $gapCounter . "_numeric_upper");
649 $upperbound->setInlineStyle('text-align: right;');
650 } else {
651 $value = new ilNumberInputGUI($this->lng->txt('value'), "gap_" . $gapCounter . "_numeric");
652 $value->allowDecimals(true);
653
654 $lowerbound = new ilNumberInputGUI($this->lng->txt('range_lower_limit'), "gap_" . $gapCounter . "_numeric_lower");
655 $lowerbound->allowDecimals(true);
656
657 $upperbound = new ilNumberInputGUI($this->lng->txt('range_upper_limit'), "gap_" . $gapCounter . "_numeric_upper");
658 $upperbound->allowDecimals(true);
659 }
660
661 $value->setSize(10);
662 $value->setValue(ilLegacyFormElementsUtil::prepareFormOutput($gap->getAnswertext()));
663 $value->setRequired(true);
664 $form->addItem($value);
665
666 $lowerbound->setSize(10);
667 $lowerbound->setRequired(true);
668 $lowerbound->setValue(ilLegacyFormElementsUtil::prepareFormOutput($gap->getLowerBound()));
669 $form->addItem($lowerbound);
670
671 $upperbound->setSize(10);
672 $upperbound->setRequired(true);
673 $upperbound->setValue(ilLegacyFormElementsUtil::prepareFormOutput($gap->getUpperBound()));
674 $form->addItem($upperbound);
675
676 if ($this->object->getFixedTextLength() > 0) {
677 $value->setSize($this->object->getFixedTextLength());
678 $value->setMaxLength($this->object->getFixedTextLength());
679 $lowerbound->setSize($this->object->getFixedTextLength());
680 $lowerbound->setMaxLength($this->object->getFixedTextLength());
681 $upperbound->setSize($this->object->getFixedTextLength());
682 $upperbound->setMaxLength($this->object->getFixedTextLength());
683 }
684
685 $points = new ilNumberInputGUI($this->lng->txt('points'), "gap_" . $gapCounter . "_numeric_points");
686 $points->allowDecimals(true);
687 $points->setSize(3);
688 $points->setRequired(true);
689 $points->setValue(ilLegacyFormElementsUtil::prepareFormOutput($gap->getPoints()));
690 $form->addItem($points);
691 return $form;
692 }
693
697 public function createGaps(): void
698 {
699 $this->setAdditionalContentEditingModeFromPost();
700 $this->writePostData(true);
701 $this->object->saveToDb();
702 $this->editQuestion();
703 }
704
705 public function removegap(): void
706 {
707 $this->setAdditionalContentEditingModeFromPost();
708 $this->writePostData(true);
709 $this->object->deleteAnswerText(
710 $this->gapIndex,
711 $this->request_data_collector->getCmdIndex('removegap_' . $this->gapIndex)
712 );
713 $this->editQuestion();
714 }
715
716 public function addgap(): void
717 {
718 $this->setAdditionalContentEditingModeFromPost();
719 $this->writePostData(true);
720 $this->object->addGapAnswer(
721 $this->gapIndex,
722 $this->request_data_collector->getCmdIndex('addgap_' . $this->gapIndex) + 1,
723 ''
724 );
725 $this->editQuestion();
726 }
727
728 public function getPreview(
729 bool $show_question_only = false,
730 bool $show_inline_feedback = false
731 ): string {
732 $user_solution = is_object($this->getPreviewSession()) ? (array) $this->getPreviewSession()->getParticipantsSolution() : [];
733
734 $template = new ilTemplate("tpl.il_as_qpl_cloze_question_output.html", true, true, "components/ILIAS/TestQuestionPool");
735 $output = $this->object->getClozeTextForHTMLOutput();
736 foreach ($this->object->getGaps() as $gap_index => $gap) {
737 switch ($gap->getType()) {
739 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_text.html", true, true, "components/ILIAS/TestQuestionPool");
740
741 $gap_size = $gap->getGapSize() > 0 ? $gap->getGapSize() : $this->object->getFixedTextLength();
742 if ($gap_size > 0) {
743 $gaptemplate->setCurrentBlock('size_and_maxlength');
744 $gaptemplate->setVariable("TEXT_GAP_SIZE", $gap_size);
745 $gaptemplate->parseCurrentBlock();
746 }
747 $gaptemplate->setVariable("GAP_COUNTER", $gap_index);
748 foreach ($user_solution as $val1 => $val2) {
749 if (strcmp($val1, $gap_index) == 0) {
750 $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilLegacyFormElementsUtil::prepareFormOutput(
751 $val2
752 ) . "\"");
753 }
754 }
755 // fau: fixGapReplace - use replace function
756 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
757 // fau.
758 break;
760 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_select.html", true, true, "components/ILIAS/TestQuestionPool");
761 foreach ($gap->getItems($this->object->getShuffler(), $gap_index) as $item) {
762 $gaptemplate->setCurrentBlock("select_gap_option");
763 $gaptemplate->setVariable("SELECT_GAP_VALUE", $item->getOrder());
764 $gaptemplate->setVariable(
765 "SELECT_GAP_TEXT",
766 ilLegacyFormElementsUtil::prepareFormOutput($item->getAnswerText())
767 );
768 foreach ($user_solution as $val1 => $val2) {
769 if (strcmp($val1, $gap_index) == 0) {
770 if (strcmp($val2, $item->getOrder()) == 0) {
771 $gaptemplate->setVariable("SELECT_GAP_SELECTED", " selected=\"selected\"");
772 }
773 }
774 }
775 $gaptemplate->parseCurrentBlock();
776 }
777 $gaptemplate->setVariable("PLEASE_SELECT", $this->lng->txt("please_select"));
778 $gaptemplate->setVariable("GAP_COUNTER", $gap_index);// fau: fixGapReplace - use replace function
779 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
780 // fau.
781 break;
783 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_numeric.html", true, true, "components/ILIAS/TestQuestionPool");
784 $gap_size = $gap->getGapSize() > 0 ? $gap->getGapSize() : $this->object->getFixedTextLength();
785 if ($gap_size > 0) {
786 $gaptemplate->setCurrentBlock('size_and_maxlength');
787 $gaptemplate->setVariable("TEXT_GAP_SIZE", $gap_size);
788 $gaptemplate->parseCurrentBlock();
789 }
790 $gaptemplate->setVariable("GAP_COUNTER", $gap_index);
791 foreach ($user_solution as $val1 => $val2) {
792 if (strcmp($val1, $gap_index) == 0) {
793 $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilLegacyFormElementsUtil::prepareFormOutput(
794 $val2
795 ) . "\"");
796 }
797 }
798 // fau: fixGapReplace - use replace function
799 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
800 // fau.
801 break;
802 }
803 }
804 $template->setVariable("QUESTIONTEXT", $this->renderLatex($this->object->getQuestionForHTMLOutput()));
805 $template->setVariable("CLOZETEXT", $this->renderLatex(ilLegacyFormElementsUtil::prepareTextareaOutput($output, true)));
806 $questionoutput = $template->get();
807 if (!$show_question_only) {
808 // get page object output
809 $questionoutput = $this->getILIASPage($questionoutput);
810 }
811 return $questionoutput;
812 }
813
814 public function getSolutionOutput(
815 int $active_id,
816 ?int $pass = null,
817 bool $graphical_output = false,
818 bool $result_output = false,
819 bool $show_question_only = true,
820 bool $show_feedback = false,
821 bool $show_correct_solution = false,
822 bool $show_manual_scoring = false,
823 bool $show_question_text = true,
824 bool $show_inline_feedback = true
825 ): string {
826 $user_solution = [];
827 if (($active_id > 0) && (!$show_correct_solution)) {
828 $user_solution = $this->object->getSolutionValues($active_id, $pass);
829 if (!is_array($user_solution)) {
830 $user_solution = [];
831 }
832 }
833
834 return $this->renderSolutionOutput(
835 $user_solution,
836 $active_id,
837 $pass,
838 $graphical_output,
839 $result_output,
840 $show_question_only,
841 $show_feedback,
842 $show_correct_solution,
843 $show_manual_scoring,
844 $show_question_text,
845 false,
846 false
847 );
848 }
849
850 public function renderSolutionOutput(
851 mixed $user_solutions,
852 int $active_id,
853 ?int $pass,
854 bool $graphical_output = false,
855 bool $result_output = false,
856 bool $show_question_only = true,
857 bool $show_feedback = false,
858 bool $show_correct_solution = false,
859 bool $show_manual_scoring = false,
860 bool $show_question_text = true,
861 bool $show_autosave_title = false,
862 bool $show_inline_feedback = false,
863 ): ?string {
864 $template = new ilTemplate("tpl.il_as_qpl_cloze_question_output_solution.html", true, true, "components/ILIAS/TestQuestionPool");
865 $output = $this->object->getClozeTextForHTMLOutput();
866 $assClozeGapCombinationObject = new assClozeGapCombination($this->db);
867 $check_for_gap_combinations = $assClozeGapCombinationObject->loadFromDb($this->object->getId());
868
869 foreach ($this->object->getGaps() as $gap_index => $gap) {
870 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_output_solution_gap.html", true, true, "components/ILIAS/TestQuestionPool");
871 $found = [];
872 foreach ($user_solutions as $solutionarray) {
873 if ($solutionarray["value1"] == $gap_index) {
874 $found = $solutionarray;
875 }
876 }
877
878 if ($active_id
879 && $graphical_output) {
880 // output of ok/not ok icons for user entered solutions
881 $details = $this->object->getUserResultDetails($active_id, $pass);
882 $check = $details[$gap_index] ?? [];
883
884 if (count($check_for_gap_combinations) != 0) {
885 $gaps_used_in_combination = $assClozeGapCombinationObject->getGapsWhichAreUsedInCombination($this->object->getId());
886 $custom_user_solution = [];
887 if (array_key_exists($gap_index, $gaps_used_in_combination)) {
888 $combination_id = $gaps_used_in_combination[$gap_index];
889 foreach ($gaps_used_in_combination as $key => $value) {
890 $a = 0;
891 if ($value == $combination_id) {
892 foreach ($user_solutions as $solution_key => $solution_value) {
893 if ($solution_value['value1'] == $key) {
894 $result_row = [];
895 $result_row['gap_id'] = $solution_value['value1'];
896 $result_row['value'] = $solution_value['value2'];
897 array_push($custom_user_solution, $result_row);
898 }
899 }
900 }
901 }
902 $points_array = $this->object->calculateCombinationResult($custom_user_solution);
903 $max_combination_points = $assClozeGapCombinationObject->getMaxPointsForCombination($this->object->getId(), $combination_id);
904 if ($points_array[0] == $max_combination_points) {
905 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK));
906 } elseif ($points_array[0] > 0) {
907 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_MOSTLY_OK));
908 } else {
909 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK));
910 }
911 } else {
912 if (array_key_exists('best', $check) && $check["best"]) {
913 $gaptemplate->setCurrentBlock("icon_ok");
914 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK));
915 $gaptemplate->parseCurrentBlock();
916 } else {
917 $gaptemplate->setCurrentBlock("icon_ok");
918 if (array_key_exists('positive', $check) && $check["positive"]) {
919 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_MOSTLY_OK));
920 } else {
921 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK));
922 }
923 $gaptemplate->parseCurrentBlock();
924 }
925 }
926 } else {
927 if (array_key_exists('best', $check) && $check["best"]) {
928 $gaptemplate->setCurrentBlock("icon_ok");
929 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK));
930 $gaptemplate->parseCurrentBlock();
931 } else {
932 $gaptemplate->setCurrentBlock("icon_ok");
933 if (array_key_exists('positive', $check) && $check["positive"]) {
934 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_MOSTLY_OK));
935 } else {
936 $gaptemplate->setVariable("ICON_OK", $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK));
937 }
938 $gaptemplate->parseCurrentBlock();
939 }
940 }
941 }
942
943 switch ($gap->getType()) {
946 $solutiontext = "";
947 if (($active_id > 0) && (!$show_correct_solution)) {
948 if ((count($found) == 0) || (strlen(trim($found["value2"])) == 0)) {
949 for ($chars = 0; $chars < $gap->getMaxWidth(); $chars++) {
950 $solutiontext .= "&nbsp;";
951 }
952 } else {
953 $solutiontext = ilLegacyFormElementsUtil::prepareFormOutput($found["value2"]);
954 }
955 } else {
956 $solutiontext = $this-> getBestSolutionText($gap, $gap_index, $check_for_gap_combinations);
957 }
958 $this->populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext);
959 // fau: fixGapReplace - use replace function
960 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
961 // fau.
962 break;
964 $solutiontext = "";
965 if (($active_id > 0) && (!$show_correct_solution)) {
966 if ((count($found) == 0) || (strlen(trim($found["value2"])) == 0)) {
967 for ($chars = 0; $chars < $gap->getMaxWidth(); $chars++) {
968 $solutiontext .= "&nbsp;";
969 }
970 } else {
971 $item = $gap->getItem($found["value2"]);
972 if (is_object($item)) {
973 $solutiontext = ilLegacyFormElementsUtil::prepareFormOutput($item->getAnswertext());
974 } else {
975 for ($chars = 0; $chars < $gap->getMaxWidth(); $chars++) {
976 $solutiontext .= "&nbsp;";
977 }
978 }
979 }
980 } else {
981 $solutiontext = $this-> getBestSolutionText($gap, $gap_index, $check_for_gap_combinations);
982 }
983 $this->populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext);
984 // fau: fixGapReplace - use replace function
985 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
986 // fau.
987 break;
988 }
989 }
990
991 if ($show_question_text) {
992 $template->setVariable("QUESTIONTEXT", $this->renderLatex($this->object->getQuestionForHTMLOutput()));
993 }
994
995 $template->setVariable("CLOZETEXT", $this->renderLatex(ilLegacyFormElementsUtil::prepareTextareaOutput($output, true)));
996
997 // generate the question output
998 $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "components/ILIAS/TestQuestionPool");
999 $questionoutput = $template->get();
1000
1001 $feedback = '';
1002 if ($show_feedback) {
1003 if (!$this->isTestPresentationContext()) {
1004 $feedback = $this->getGenericFeedbackOutput((int) $active_id, $pass);
1005 }
1006
1007 $feedback .= $this->getSpecificFeedbackOutput(
1008 $this->object->fetchIndexedValuesFromValuePairs($user_solutions)
1009 );
1010 }
1011 if ($feedback !== '') {
1012 $cssClass = (
1013 $this->hasCorrectSolution($active_id, $pass) ?
1015 );
1016
1017 $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
1018 $solutiontemplate->setVariable("FEEDBACK", $this->renderLatex(
1020 ));
1021 }
1022
1023 $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
1024
1025 $solutionoutput = $solutiontemplate->get();
1026
1027 if (!$show_question_only) {
1028 // get page object output
1029 $solutionoutput = $this->getILIASPage($solutionoutput);
1030 }
1031
1032 return $solutionoutput;
1033 }
1034
1041 protected function getBestSolutionText($gap, $gap_index, $gap_combinations): string
1042 {
1043 $combination = null;
1044 foreach ((array) $gap_combinations as $combiGapSolRow) {
1045 if ($combiGapSolRow['gap_fi'] == $gap_index && $combiGapSolRow['best_solution']) {
1046 $combination = $combiGapSolRow;
1047 break;
1048 }
1049 }
1050 $best_solution_text = ilLegacyFormElementsUtil::prepareFormOutput(
1051 $gap->getBestSolutionOutput(
1052 $this->object->getShuffler(),
1053 $combination
1054 )
1055 );
1056 return $best_solution_text;
1057 }
1058
1059 public function getTestOutput(
1060 int $active_id,
1061 int $pass,
1062 bool $is_question_postponed = false,
1063 array|bool $user_post_solutions = false,
1064 bool $show_specific_inline_feedback = false
1065 ): string {
1066 // get the solution of the user for the active pass or from the last pass if allowed
1067 $user_solution = [];
1068 if ($user_post_solutions !== false) {
1069 $indexedSolution = $this->object->fetchSolutionSubmit();
1070 $user_solution = $this->object->fetchValuePairsFromIndexedValues($indexedSolution);
1071 } elseif ($active_id) {
1072 $user_solution = $this->object->getTestOutputSolutions($active_id, $pass);
1073 // hey.
1074 if (!is_array($user_solution)) {
1075 $user_solution = [];
1076 }
1077 }
1078
1079 $template = new ilTemplate("tpl.il_as_qpl_cloze_question_output.html", true, true, "components/ILIAS/TestQuestionPool");
1080 $output = $this->object->getClozeTextForHTMLOutput();
1081 foreach ($this->object->getGaps() as $gap_index => $gap) {
1082 switch ($gap->getType()) {
1084 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_text.html", true, true, "components/ILIAS/TestQuestionPool");
1085 $gap_size = $gap->getGapSize() > 0 ? $gap->getGapSize() : $this->object->getFixedTextLength();
1086
1087 if ($gap_size > 0) {
1088 $gaptemplate->setCurrentBlock('size_and_maxlength');
1089 $gaptemplate->setVariable("TEXT_GAP_SIZE", $gap_size);
1090 $gaptemplate->parseCurrentBlock();
1091 }
1092
1093 $gaptemplate->setVariable("GAP_COUNTER", $gap_index);
1094 foreach ($user_solution as $solution) {
1095 if (strcmp($solution["value1"], $gap_index) == 0) {
1096 $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilLegacyFormElementsUtil::prepareFormOutput(
1097 $solution["value2"]
1098 ) . "\"");
1099 }
1100 }
1101 // fau: fixGapReplace - use replace function
1102 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
1103 // fau.
1104 break;
1106 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_select.html", true, true, "components/ILIAS/TestQuestionPool");
1107 foreach ($gap->getItems($this->object->getShuffler(), $gap_index) as $item) {
1108 $gaptemplate->setCurrentBlock("select_gap_option");
1109 $gaptemplate->setVariable("SELECT_GAP_VALUE", $item->getOrder());
1110 $gaptemplate->setVariable(
1111 "SELECT_GAP_TEXT",
1112 ilLegacyFormElementsUtil::prepareFormOutput($item->getAnswerText())
1113 );
1114 foreach ($user_solution as $solution) {
1115 if (strcmp($solution["value1"], $gap_index) == 0) {
1116 if (strcmp($solution["value2"], $item->getOrder()) == 0) {
1117 $gaptemplate->setVariable("SELECT_GAP_SELECTED", " selected=\"selected\"");
1118 }
1119 }
1120 }
1121 $gaptemplate->parseCurrentBlock();
1122 }
1123 $gaptemplate->setVariable("PLEASE_SELECT", $this->lng->txt("please_select"));
1124 $gaptemplate->setVariable("GAP_COUNTER", $gap_index);// fau: fixGapReplace - use replace function
1125 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
1126 // fau.
1127 break;
1129 $gaptemplate = new ilTemplate("tpl.il_as_qpl_cloze_question_gap_numeric.html", true, true, "components/ILIAS/TestQuestionPool");
1130 $gap_size = $gap->getGapSize() > 0 ? $gap->getGapSize() : $this->object->getFixedTextLength();
1131 if ($gap_size > 0) {
1132 $gaptemplate->setCurrentBlock('size_and_maxlength');
1133 $gaptemplate->setVariable("TEXT_GAP_SIZE", $gap_size);
1134 $gaptemplate->parseCurrentBlock();
1135 }
1136
1137 $gaptemplate->setVariable("GAP_COUNTER", $gap_index);
1138 foreach ($user_solution as $solution) {
1139 if (strcmp($solution["value1"], $gap_index) == 0) {
1140 $gaptemplate->setVariable("VALUE_GAP", " value=\"" . ilLegacyFormElementsUtil::prepareFormOutput(
1141 $solution["value2"]
1142 ) . "\"");
1143 }
1144 }
1145 // fau: fixGapReplace - use replace function
1146 $output = $this->object->replaceFirstGap($output, $gaptemplate->get());
1147 // fau.
1148 break;
1149 }
1150 }
1151
1152 $template->setVariable("QUESTIONTEXT", $this->renderLatex($this->object->getQuestionForHTMLOutput()));
1153 $template->setVariable("CLOZETEXT", $this->renderLatex(ilLegacyFormElementsUtil::prepareTextareaOutput($output, true)));
1154 $questionoutput = $template->get();
1155 $pageoutput = $this->outQuestionPage("", $is_question_postponed, $active_id, $questionoutput);
1156 return $pageoutput;
1157 }
1158
1159 public function getSpecificFeedbackOutput(array $user_solution): string
1160 {
1161 if (!$this->object->feedbackOBJ->isSpecificAnswerFeedbackAvailable($this->object->getId())) {
1162 return '';
1163 }
1164
1165 $feedback = '<table class="test_specific_feedback"><tbody>';
1166
1167 foreach ($this->object->gaps as $gap_index => $gap) {
1168 $answer_value = $this->object->fetchAnswerValueForGap($user_solution, $gap_index);
1169 if ($gap->getType() !== assClozeGap::TYPE_TEXT
1170 && $answer_value === '') {
1171 continue;
1172 }
1173 $answer_index = $this->object->feedbackOBJ->determineAnswerIndexForAnswerValue($gap, $answer_value);
1174 $fb = $this->object->feedbackOBJ->determineTestOutputGapFeedback($gap_index, $answer_index);
1175
1176 $caption = $this->lng->txt('gap') . ' ' . ($gap_index + 1) . ': ';
1177 $feedback .= '<tr><td>';
1178 $feedback .= $caption . '</td><td>';
1179 $feedback .= $fb . '</td> </tr>';
1180 }
1181 $feedback .= '</tbody></table>';
1182
1183 return $this->renderLatex(ilLegacyFormElementsUtil::prepareTextareaOutput($feedback, true));
1184 }
1185
1196 {
1197 return [];
1198 }
1199
1210 {
1211 return [];
1212 }
1213
1220 public function getAggregatedAnswersView(array $relevant_answers): string
1221 {
1222 $overview = [];
1223 $aggregation = [];
1224 foreach ($relevant_answers as $answer) {
1225 $overview[$answer['active_fi']][$answer['pass']][$answer['value1']] = $answer['value2'];
1226 }
1227
1228 foreach ($overview as $active) {
1229 foreach ($active as $answer) {
1230 foreach ($answer as $option => $value) {
1231 $aggregation[$option][$value] = $aggregation[$option][$value] + 1;
1232 }
1233 }
1234 }
1235
1236 $html = '<div>';
1237 $i = 0;
1238 foreach ($this->object->getGaps() as $gap_index => $gap) {
1239 if ($gap->type == assClozeGap::TYPE_SELECT) {
1240 $html .= '<p>Gap ' . ($i + 1) . ' - SELECT</p>';
1241 $html .= '<ul>';
1242 $j = 0;
1243 foreach ($gap->getItems($this->object->getShuffler(), $gap_index) as $gap_item) {
1244 $aggregate = $aggregation[$i];
1245 $html .= '<li>' . $gap_item->getAnswerText() . ' - ' . ($aggregate[$j] ? $aggregate[$j] : 0) . '</li>';
1246 $j++;
1247 }
1248 $html .= '</ul>';
1249 }
1250
1251 if ($gap->type == assClozeGap::TYPE_TEXT) {
1252 $present_elements = [];
1253 foreach ($gap->getItems($this->randomGroup->shuffleArray(new Seed\RandomSeed())) as $item) {
1255 $present_elements[] = $item->getAnswertext();
1256 }
1257
1258 $html .= '<p>Gap ' . ($i + 1) . ' - TEXT</p>';
1259 $html .= '<ul>';
1260 $aggregate = (array) $aggregation[$i];
1261 foreach ($aggregate as $answer => $count) {
1262 $show_mover = '';
1263 if (in_array($answer, $present_elements)) {
1264 $show_mover = ' style="display: none;" ';
1265 }
1266
1267 $html .= '<li>' . $answer . ' - ' . $count
1268 . '&nbsp;<button class="clone_fields_add btn btn-link" ' . $show_mover . ' data-answer="' . $answer . '" name="add_gap_' . $i . '_0">
1269 <span class="sr-only"></span><span class="glyphicon glyphicon-plus"></span></button>
1270 </li>';
1271 }
1272 $html .= '</ul>';
1273 }
1274
1275 if ($gap->type == assClozeGap::TYPE_NUMERIC) {
1276 $html .= '<p>Gap ' . ($i + 1) . ' - NUMERIC</p>';
1277 $html .= '<ul>';
1278 $j = 0;
1279 foreach ($gap->getItems($this->object->getShuffler(), $gap_index) as $gap_item) {
1280 $aggregate = (array) $aggregation[$i];
1281 foreach ($aggregate as $answer => $count) {
1282 $html .= '<li>' . $answer . ' - ' . $count . '</li>';
1283 }
1284 $j++;
1285 }
1286 $html .= '</ul>';
1287 }
1288 $i++;
1289 $html .= '<hr />';
1290 }
1291
1292 $html .= '</div>';
1293 return $html;
1294 }
1295
1296 public function applyIndizesToGapText($question_text): string
1297 {
1298 $parts = explode('[gap', $question_text);
1299 $i = 0;
1300 $question_text = '';
1301 foreach ($parts as $part) {
1302 if ($i == 0) {
1303 $question_text .= $part;
1304 } else {
1305 $question_text .= '[gap ' . $i . $part;
1306 }
1307 $i++;
1308 }
1309 return $question_text;
1310 }
1311
1312 public function removeIndizesFromGapText($question_text): string
1313 {
1314 $parts = preg_split('/\[gap \d*\]/', $question_text);
1315 $question_text = implode('[gap]', $parts);
1316 return $question_text;
1317 }
1318
1323 private function populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext): void
1324 {
1325 if ($this->isRenderPurposePrintPdf()) {
1326 $gaptemplate->setCurrentBlock('gap_span');
1327 $gaptemplate->setVariable('SPAN_SOLUTION', $solutiontext);
1328 } elseif ($gap->getType() == assClozeGap::TYPE_SELECT) {
1329 $gaptemplate->setCurrentBlock('gap_select');
1330 $gaptemplate->setVariable('SELECT_SOLUTION', $solutiontext);
1331 } else {
1332 $gap_size = $gap->getGapSize() > 0 ? $gap->getGapSize() : $this->object->getFixedTextLength();
1333
1334 if ($gap_size > 0) {
1335 $gaptemplate->setCurrentBlock('gap_size');
1336 $gaptemplate->setVariable("GAP_SIZE", $gap_size);
1337 $gaptemplate->parseCurrentBlock();
1338 }
1339
1340 $gaptemplate->setCurrentBlock('gap_input');
1341 $gaptemplate->setVariable('INPUT_SOLUTION', $solutiontext);
1342 }
1343
1344
1345 $gaptemplate->parseCurrentBlock();
1346 }
1347
1348 protected function hasAddAnswerAction($relevantAnswers, $questionIndex): bool
1349 {
1350 foreach ($this->getAnswersFrequency($relevantAnswers, $questionIndex) as $answer) {
1351 if (isset($answer['actions'])) {
1352 return true;
1353 }
1354 }
1355
1356 return false;
1357 }
1358
1359 public function getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex): ilAnswerFrequencyStatisticTableGUI
1360 {
1361 $table = parent::getAnswerFrequencyTableGUI(
1362 $parentGui,
1363 $parentCmd,
1364 $relevantAnswers,
1365 $questionIndex
1366 );
1367
1368 $table->setTitle(
1369 sprintf(
1370 $this->lng->txt('tst_corrections_answers_tbl_subindex'),
1371 $this->lng->txt('gap') . ' ' . ($questionIndex + 1)
1372 )
1373 );
1374
1375 if ($this->hasAddAnswerAction($relevantAnswers, $questionIndex)) {
1376 $table->addColumn('', '', '200');
1377 }
1378
1379 return $table;
1380 }
1381
1382 public function getSubQuestionsIndex(): array
1383 {
1384 return array_keys($this->object->getGaps());
1385 }
1386
1387 protected function getAnswerTextLabel($gapIndex, $answer)
1388 {
1389 $gap = $this->object->getGap($gapIndex);
1390
1391 switch ($gap->type) {
1394 return $answer;
1395
1397 default:
1398 $items = $gap->getItems($this->randomGroup->dontShuffle());
1399 return $items[$answer]->getAnswertext();
1400 }
1401 }
1402
1403 protected function completeAddAnswerAction($answers, $gap_index): array
1404 {
1405 $gap = $this->object->getGap($gap_index);
1406
1407 if ($gap->type != assClozeGap::TYPE_TEXT ||
1408 $this->isUsedInCombinations($gap_index)) {
1409 return $answers;
1410 }
1411
1412 foreach ($answers as $key => $ans) {
1413 $found = false;
1414
1415 foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $item) {
1416 if ($ans['answer'] !== $item->getAnswerText()) {
1417 continue;
1418 }
1419
1420 $found = true;
1421 break;
1422 }
1423
1424 if (!$found) {
1425 $answers[$key]['addable'] = true;
1426 }
1427 }
1428
1429 return $answers;
1430 }
1431
1432 public function getAnswersFrequency($relevantAnswers, $questionIndex): array
1433 {
1434 $answers = [];
1435
1436 foreach ($relevantAnswers as $row) {
1437 if ($row['value1'] != $questionIndex) {
1438 continue;
1439 }
1440
1441 if (!isset($answers[$row['value2']])) {
1442 $label = $this->getAnswerTextLabel($row['value1'], $row['value2']);
1443
1444 $answers[$row['value2']] = [
1445 'answer' => $label, 'frequency' => 0
1446 ];
1447 }
1448
1449 $answers[$row['value2']]['frequency']++;
1450 }
1451
1452 $answers = $this->completeAddAnswerAction($answers, $questionIndex);
1453
1454 return $answers;
1455 }
1456
1457 protected function isUsedInCombinations($gapIndex): bool
1458 {
1459 foreach ($this->object->getGapCombinations() as $combination) {
1460 if ($combination['gap_fi'] != $gapIndex) {
1461 continue;
1462 }
1463
1464 return true;
1465 }
1466
1467 return false;
1468 }
1469
1470 protected function getGapCombinations(): array
1471 {
1472 $combinations = [];
1473
1474 foreach ($this->object->getGapCombinations() as $c) {
1475 if (!isset($combinations[$c['cid']])) {
1476 $combinations[$c['cid']] = [];
1477 }
1478
1479 if (!isset($combinations[$c['cid']][$c['row_id']])) {
1480 $combinations[$c['cid']][$c['row_id']] = [
1481 'gaps' => [], 'points' => $c['points'],
1482 ];
1483 }
1484
1485 if (!isset($combinations[$c['cid']][$c['row_id']]['gaps'][$c['gap_fi']])) {
1486 $combinations[$c['cid']][$c['row_id']]['gaps'][$c['gap_fi']] = [];
1487 }
1488
1489 $combinations[$c['cid']][$c['row_id']]['gaps'][$c['gap_fi']] = $c['answer'];
1490 }
1491
1492 return $combinations;
1493 }
1494
1496 {
1497 foreach ($this->object->getGaps() as $gapIndex => $gap) {
1498 $this->populateGapCorrectionFormProperties(
1499 $form,
1500 $gap,
1501 $gapIndex,
1502 $this->isUsedInCombinations($gapIndex)
1503 );
1504 }
1505
1506 if ($this->object->getGapCombinationsExists()) {
1507 foreach ($this->getGapCombinations() as $combiIndex => $gapCombination) {
1508 $this->populateGapCombinationCorrectionFormProperty($form, $gapCombination, $combiIndex);
1509 }
1510 }
1511 }
1512
1513 protected function populateGapCombinationCorrectionFormProperty(ilPropertyFormGUI $form, $gapCombi, $combiIndex): void
1514 {
1515 $header = new ilFormSectionHeaderGUI();
1516 $header->setTitle("Gap Combination " . ($combiIndex + 1));
1517 $form->addItem($header);
1518
1519 $inp = new ilAssClozeTestCombinationVariantsInputGUI('Answers', 'combination_' . $combiIndex);
1520 $inp->setValues($gapCombi);
1521 $form->addItem($inp);
1522 }
1523
1529 protected function populateGapCorrectionFormProperties($form, $gap, $gapIndex, $hidePoints): void
1530 {
1531 $header = new ilFormSectionHeaderGUI();
1532 $header->setTitle($this->lng->txt("gap") . " " . ($gapIndex + 1));
1533 $form->addItem($header);
1534
1535 if ($gap->getType() == assClozeGap::TYPE_TEXT || $gap->getType() == assClozeGap::TYPE_SELECT) {
1536 $this->populateTextOrSelectGapCorrectionFormProperty($form, $gap, $gapIndex, $hidePoints);
1537 } elseif ($gap->getType() == assClozeGap::TYPE_NUMERIC) {
1538 foreach ($gap->getItemsRaw() as $item) {
1539 $this->populateNumericGapCorrectionFormProperty($form, $item, $gapIndex, $hidePoints);
1540 }
1541 }
1542 }
1543
1544 protected function populateTextOrSelectGapCorrectionFormProperty($form, $gap, $gapIndex, $hidePoints): void
1545 {
1546 $values = new ilAssAnswerCorrectionsInputGUI($this->lng->txt("values"), "gap_" . $gapIndex);
1547 $values->setHidePointsEnabled($hidePoints);
1548 $values->setRequired(true);
1549 $values->setQuestionObject($this->object);
1550 $values->setValues($gap->getItemsRaw());
1551 $form->addItem($values);
1552 }
1553
1554 protected function populateNumericGapCorrectionFormProperty($form, $item, $gapIndex, $hidePoints): void
1555 {
1556 $value = new ilNumberInputGUI($this->lng->txt('value'), "gap_" . $gapIndex . "_numeric");
1557 $value->allowDecimals(true);
1558 $value->setSize(10);
1559 $value->setValue(ilLegacyFormElementsUtil::prepareFormOutput($item->getAnswertext()));
1560 $value->setRequired(true);
1561 $form->addItem($value);
1562
1563 $lowerbound = new ilNumberInputGUI($this->lng->txt('range_lower_limit'), "gap_" . $gapIndex . "_numeric_lower");
1564 $lowerbound->allowDecimals(true);
1565 $lowerbound->setSize(10);
1566 $lowerbound->setRequired(true);
1567 $lowerbound->setValue(ilLegacyFormElementsUtil::prepareFormOutput($item->getLowerBound()));
1568 $form->addItem($lowerbound);
1569
1570 $upperbound = new ilNumberInputGUI($this->lng->txt('range_upper_limit'), "gap_" . $gapIndex . "_numeric_upper");
1571 $upperbound->allowDecimals(true);
1572 $upperbound->setSize(10);
1573 $upperbound->setRequired(true);
1574 $upperbound->setValue(ilLegacyFormElementsUtil::prepareFormOutput($item->getUpperBound()));
1575 $form->addItem($upperbound);
1576
1577 if (!$hidePoints) {
1578 $points = new ilNumberInputGUI($this->lng->txt('points'), "gap_" . $gapIndex . "_numeric_points");
1579 $points->allowDecimals(true);
1580 $points->setSize(3);
1581 $points->setRequired(true);
1582 $points->setValue(ilLegacyFormElementsUtil::prepareFormOutput($item->getPoints()));
1583 $form->addItem($points);
1584 }
1585 }
1586
1591 {
1592 foreach ($this->object->getGaps() as $gapIndex => $gap) {
1593 if ($this->isUsedInCombinations($gapIndex)) {
1594 continue;
1595 }
1596
1597 $this->saveGapCorrectionFormProperty($form, $gap, $gapIndex);
1598 }
1599
1600 if ($this->object->getGapCombinationsExists()) {
1601 $this->saveGapCombinationCorrectionFormProperties($form);
1602 }
1603 }
1604
1605 protected function saveGapCorrectionFormProperty(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex): void
1606 {
1607 if ($gap->getType() == assClozeGap::TYPE_TEXT || $gap->getType() == assClozeGap::TYPE_SELECT) {
1608 $this->saveTextOrSelectGapCorrectionFormProperty($form, $gap, $gapIndex);
1609 } elseif ($gap->getType() == assClozeGap::TYPE_NUMERIC) {
1610 foreach ($gap->getItemsRaw() as $item) {
1611 $this->saveNumericGapCorrectionFormProperty($form, $item, $gapIndex);
1612 }
1613 }
1614 }
1615
1616 protected function saveTextOrSelectGapCorrectionFormProperty(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex): void
1617 {
1618 $answers = $form->getItemByPostVar('gap_' . $gapIndex)->getValues();
1619
1620 foreach ($gap->getItemsRaw() as $index => $item) {
1621 $item->setPoints((float) str_replace(',', '.', $answers[$index]->getPoints()));
1622 }
1623 }
1624
1625 protected function saveNumericGapCorrectionFormProperty(ilPropertyFormGUI $form, assAnswerCloze $item, $gapIndex): void
1626 {
1627 $item->setAnswertext($form->getInput('gap_' . $gapIndex . '_numeric'));
1628 $item->setLowerBound($form->getInput('gap_' . $gapIndex . '_numeric_lower'));
1629 $item->setUpperBound($form->getInput('gap_' . $gapIndex . '_numeric_upper'));
1630 $item->setPoints((float) str_replace(',', '.', $form->getInput('gap_' . $gapIndex . '_numeric_points')));
1631 }
1632
1634 {
1635 // please dont ask (!) -.-
1636
1637 $combinationPoints = ['points' => [], 'select' => []];
1638 $combinationValues = [];
1639
1640 foreach ($this->getGapCombinations() as $combiId => $combi) {
1641 $values = $form->getItemByPostVar('combination_' . $combiId)->getValues();
1642
1643 if (!isset($combinationPoints['points'][$combiId])) {
1644 $combinationPoints['points'][$combiId] = [];
1645 $combinationPoints['select'][$combiId] = [];
1646 $combinationValues[$combiId] = [];
1647 }
1648
1649 foreach ($combi as $varId => $variant) {
1650 $combinationPoints['points'][$combiId][$varId] = (float) str_replace(',', '.', $values[$varId]['points']);
1651 $combinationPoints['select'][$combiId] = array_keys($values[$varId]['gaps']);
1652 $combinationValues[$combiId][$varId] = array_values($values[$varId]['gaps']);
1653 }
1654 }
1655
1656 $assClozeGapCombinationObject = new assClozeGapCombination($this->db);
1657 $assClozeGapCombinationObject->clearGapCombinationsFromDb($this->object->getId());
1658
1659 $assClozeGapCombinationObject->saveGapCombinationToDb(
1660 $this->object->getId(),
1661 $combinationPoints,
1662 $combinationValues
1663 );
1664 $this->object->setGapCombinationsExists(true);
1665 }
1666}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$check
Definition: buildRTE.php:81
setAnswertext($answertext="")
Sets the answer text.
setPoints($points=0.0)
Sets the points.
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
return true
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setUpperBound(string $bound)
Sets the upper bound.
setLowerBound(string $bound)
Sets the lower boind.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class for cloze question gaps.
getItemsRaw()
Gets the items of a cloze gap.
Cloze test question GUI representation.
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,)
hasAddAnswerAction($relevantAnswers, $questionIndex)
applyIndizesToGapText($question_text)
populateGapCombinationCorrectionFormProperty(ilPropertyFormGUI $form, $gapCombi, $combiIndex)
populateTextOrSelectGapCorrectionFormProperty($form, $gap, $gapIndex, $hidePoints)
hasErrorInGapCombinationPoints(array $gap_combinations)
populateNumericGapCorrectionFormProperty($form, $item, $gapIndex, $hidePoints)
getAnswerTextLabel($gapIndex, $answer)
getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex)
writePostData(bool $always=false)
{Evaluates a posted edit form and writes the form data in the question object.integer A positive valu...
populateSelectGapFormPart($form, $gap, $gapCounter)
Populates the form-part for a select gap.
populateNumericGapFormPart($form, $gap, $gapCounter)
Populates the form-part for a numeric gap.
createGaps()
Create gaps from cloze text.
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)
getAfterParticipationSuppressionQuestionPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
$gapIndex
A temporary variable to store gap indexes of ilCtrl commands in the getCommand method.
removeIndizesFromGapText($question_text)
populateGapCorrectionFormProperties($form, $gap, $gapIndex, $hidePoints)
writeQuestionSpecificPostData(ilPropertyFormGUI $form)
Extracts the question specific values from the request and applies them to the data object.
__construct(int $id=-1)
assClozeTestGUI constructor
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
populateQuestionSpecificFormPart(ilPropertyFormGUI $form)
Adds the question specific forms parts to a question property form gui.
getPreview(bool $show_question_only=false, bool $show_inline_feedback=false)
populateGapSizeFormPart($form, $gap, $gapCounter)
getAddGapButtonClickClosure(string $gap_type)
editQuestion(bool $checkonly=false, ?bool $is_save_cmd=null)
Creates an output of the edit form for the question.
getBestSolutionText($gap, $gap_index, $gap_combinations)
populateSolutiontextToGapTpl($gaptemplate, $gap, $solutiontext)
getSpecificFeedbackOutput(array $user_solution)
Returns the answer specific feedback for the question.
populateTextGapFormPart($form, $gap, $gapCounter)
Populates the form-part for a text gap.
saveCorrectionsFormProperties(ilPropertyFormGUI $form)
populateGapFormPart($form, $gapCounter)
Populates a gap form-part.
getAnswersFrequency($relevantAnswers, $questionIndex)
getAfterParticipationSuppressionAnswerPostVars()
Returns a list of postvars which will be suppressed in the form output when used in scoring adjustmen...
saveGapCombinationCorrectionFormProperties(ilPropertyFormGUI $form)
completeAddAnswerAction($answers, $gap_index)
writeAnswerSpecificPostData(ilPropertyFormGUI $form)
Extracts the answer specific values from the request and applies them to the data object.
saveNumericGapCorrectionFormProperty(ilPropertyFormGUI $form, assAnswerCloze $item, $gapIndex)
populateAnswerSpecificFormPart(ilPropertyFormGUI $form)
Adds the answer specific form parts to a question property form gui.
saveGapCorrectionFormProperty(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex)
getTestOutput(int $active_id, int $pass, bool $is_question_postponed=false, array|bool $user_post_solutions=false, bool $show_specific_inline_feedback=false)
saveTextOrSelectGapCorrectionFormProperty(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex)
Class for cloze tests.
populateTaxonomyFormSection(ilPropertyFormGUI $form)
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
renderEditForm(ilPropertyFormGUI $form)
addQuestionFormCommandButtons(ilPropertyFormGUI $form)
const ADDITIONAL_CONTENT_EDITING_MODE_IPE
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class represents a single choice wizard property in a property form.
static getSelfAssessmentTags()
Get tags allowed in question tags in self assessment mode.
This class represents a checkbox property in a property form.
This class represents a custom property in a property form.
This class represents a section header in a property form.
This class represents a formula text property in a property form.
This class represents a hidden form property 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 number property in a property form.
This class represents a property form user interface.
getInput(string $a_post_var, bool $ensureValidation=true)
Returns the input of an item, if item provides getInput method and as fallback the value of the HTTP-...
getItemByPostVar(string $a_post_var)
static _getUsedHTMLTags(string $module='')
This class represents a selection list property in a property form.
special template class to simplify handling of ITX/PEAR
This class represents a text area property in a property form.
$c
Definition: deliver.php:25
An entity that renders components to a string output.
Definition: Renderer.php:31
Interface ilDBInterface.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:61
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
if(!file_exists('../ilias.ini.php'))
global $DIC
Definition: shib_login.php:26