ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
class.assFormulaQuestionGUI.php
Go to the documentation of this file.
1<?php
2
19use ILIAS\UI\Factory as UIFactory;
20use ILIAS\UI\Renderer as UIRenderer;
21
32{
33 protected const HAS_SPECIAL_QUESTION_COMMANDS = true;
34
35 private UIFactory $ui_factory;
36 private UIRenderer $ui_renderer;
37
44 public function __construct($id = -1)
45 {
47 global $DIC;
48 $this->ui_factory = $DIC->ui()->factory();
49 $this->ui_renderer = $DIC->ui()->renderer();
50
51 $this->object = new assFormulaQuestion();
52 if ($id >= 0) {
53 $this->object->loadFromDb($id);
54 }
55 }
56
57 protected function setQuestionSpecificTabs(ilTabsGUI $ilTabs): void
58 {
59 $this->ctrl->setParameterByClass(ilLocalUnitConfigurationGUI::class, 'q_id', $this->object->getId());
60 $ilTabs->addTarget('units', $this->ctrl->getLinkTargetByClass(ilLocalUnitConfigurationGUI::class, ''), '', 'illocalunitconfigurationgui');
61 }
62
63 public function suggestRange(): void
64 {
66 $suggest_range_for_result = $this->request_data_collector->string('suggest_range_for');
67 if ($this->writePostData()) {
68 $this->tpl->setOnScreenMessage('info', $this->getErrorMessage());
69 }
71 $this->editQuestion(false, null, $suggest_range_for_result);
72 }
73
77 protected function writePostData(bool $always = false): int
78 {
79 $hasErrors = (!$always) ? $this->editQuestion(true) : false;
80 $checked = true;
81 if (!$hasErrors) {
82 $this->object->setTitle($this->request_data_collector->string('title'));
83 $this->object->setAuthor($this->request_data_collector->string('author'));
84 $this->object->setComment($this->request_data_collector->string('comment'));
85 $this->object->setQuestion($this->request_data_collector->string('question'));
86
87 $this->object->parseQuestionText();
88 $found_vars = [];
89 $found_results = [];
90
91 foreach ($this->request_data_collector->getParsedBody() as $key => $value) {
92 if (preg_match("/^unit_(\\\$v\d+)$/", $key, $matches)) {
93 array_push($found_vars, $matches[1]);
94 }
95 if (preg_match("/^unit_(\\\$r\d+)$/", $key, $matches)) {
96 array_push($found_results, $matches[1]);
97 }
98 }
99
100 try {
101 $lifecycle = ilAssQuestionLifecycle::getInstance($this->request_data_collector->string('lifecycle'));
102 $this->object->setLifecycle($lifecycle);
104 }
105
106 if (!$this->object->checkForDuplicateResults()) {
107 $this->addErrorMessage($this->lng->txt("err_duplicate_results"));
108 $checked = false;
109 }
110
111 foreach ($found_vars as $variable) {
112 if ($this->object->getVariable($variable) != null) {
113 $unit = $this->request_data_collector->int("unit_{$variable}");
114 $varObj = new assFormulaQuestionVariable(
115 $variable,
116 $this->request_data_collector->float("range_min_{$variable}") ?? 0.0,
117 $this->request_data_collector->float("range_max_{$variable}") ?? 0.0,
118 $unit !== null ? $this->object->getUnitrepository()->getUnit(
119 $unit
120 ) : null,
121 $this->request_data_collector->float("precision_{$variable}"),
122 $this->request_data_collector->float("intprecision_{$variable}")
123 );
124 $this->object->addVariable($varObj);
125 }
126 }
127
128 $tmp_form_vars = [];
129 $tmp_quest_vars = [];
130 foreach ($found_results as $result) {
131 $tmp_res_match = preg_match_all(
132 '/([$][v][0-9]*)/',
133 $this->request_data_collector->string("formula_{$result}"),
134 $form_vars
135 );
136 $tmp_form_vars = array_merge($tmp_form_vars, $form_vars[0]);
137
138 $tmp_que_match = preg_match_all(
139 '/([$][v][0-9]*)/',
140 $this->request_data_collector->string('question'),
141 $quest_vars
142 );
143 $tmp_quest_vars = array_merge($tmp_quest_vars, $quest_vars[0]);
144 }
145 $result_has_undefined_vars = array_diff($tmp_form_vars, $found_vars);
146 $question_has_unused_vars = array_diff($tmp_quest_vars, $tmp_form_vars);
147
148 if ($result_has_undefined_vars !== [] || $question_has_unused_vars !== []) {
149 $error_message = '';
150 if (count($result_has_undefined_vars) > 0) {
151 $error_message .= $this->lng->txt("res_contains_undef_var") . '<br>';
152 }
153 if (count($question_has_unused_vars) > 0) {
154 $error_message .= $this->lng->txt("que_contains_unused_var");
155 }
156 $checked = false;
157 if ($this->isSaveCommand()) {
158 $this->tpl->setOnScreenMessage('failure', $error_message);
159 }
160 }
161 foreach ($found_results as $result) {
162 if ($this->object->getResult($result) != null) {
163 $unit = $this->request_data_collector->int("unit_{$result}");
164 $resObj = new assFormulaQuestionResult(
165 $result,
166 $this->request_data_collector->float("range_min_{$result}") ?? 0.0,
167 $this->request_data_collector->float("range_max_{$result}") ?? 0.0,
168 $this->request_data_collector->float("tolerance_{$result}") ?? 0.0,
169 $unit !== null ? $this->object->getUnitrepository()->getUnit(
170 $unit
171 ) : null,
172 $this->request_data_collector->string("formula_{$result}"),
173 $this->request_data_collector->float("points_{$result}"),
174 $this->request_data_collector->float("precision_{$result}"),
175 $this->request_data_collector->int("rating_advanced_{$result}") !== 1,
176 $this->request_data_collector->int("rating_advanced_{$result}") === 1 ? $this->request_data_collector->float("rating_sign_{$result}") : null,
177 $this->request_data_collector->int("rating_advanced_{$result}") === 1 ? $this->request_data_collector->float("rating_value_{$result}") : null,
178 $this->request_data_collector->int("rating_advanced_{$result}") === 1 ? $this->request_data_collector->float("rating_unit_{$result}") : null,
179 $this->request_data_collector->int("result_type_{$result}")
180 );
181 $this->object->addResult($resObj);
182 $available_units = $this->request_data_collector->intArray("units_{$result}");
183 if (!empty($available_units)) {
184 $this->object->addResultUnits($resObj, $available_units);
185 }
186 }
187 }
188 if ($checked === false) {
189 $this->editQuestion();
190 return 1;
191 } else {
194 return 0;
195 }
196 } else {
197 return 1;
198 }
199 }
200
201 public function editQuestion(
202 bool $checkonly = false,
203 ?bool $is_save_cmd = null,
204 ?string $suggest_range_for_result = null
205 ): bool {
206 $save = $is_save_cmd ?? $this->isSaveCommand();
207
208 $form = new ilPropertyFormGUI();
209 $this->editForm = $form;
210
211 $form->setFormAction($this->ctrl->getFormAction($this));
212 $form->setTitle($this->outQuestionType());
213 $form->setMultipart(false);
214 $form->setTableWidth('100%');
215 $form->setId('assformulaquestion');
216
217 $this->addBasicQuestionFormProperties($form);
218
219 // Add info text
220 $question = $form->getItemByPostVar('question');
221 $question->setInfo($this->lng->txt('fq_question_desc'));
222
223 $variables = $this->object->getVariables();
224 $categorized_units = $this->object->getUnitrepository()->getCategorizedUnits();
225 $result_units = $this->object->getAllResultUnits();
226
227 $unit_options = [];
228 $category_name = '';
229 $new_category = false;
230 foreach ($categorized_units as $item) {
234 if ($item instanceof assFormulaQuestionUnitCategory) {
235 if ($category_name != $item->getDisplayString()) {
236 $new_category = true;
237 $category_name = $item->getDisplayString();
238 }
239 continue;
240 }
241 $unit_options[$item->getId()] = $item->getDisplayString() . ($new_category ? ' (' . $category_name . ')' : '');
242 $new_category = false;
243 }
244
245 if ($variables !== []) {
246 uasort($variables, function (assFormulaQuestionVariable $v1, assFormulaQuestionVariable $v2) {
247 $num_v1 = (int) substr($v1->getVariable(), 2);
248 $num_v2 = (int) substr($v2->getVariable(), 2);
249 if ($num_v1 > $num_v2) {
250 return 1;
251 } elseif ($num_v1 < $num_v2) {
252 return -1;
253 }
254
255 return 0;
256 });
257
258 foreach ($variables as $variable) {
262 $variable_header = new ilFormSectionHeaderGUI();
263 $variable_header->setTitle(sprintf($this->lng->txt('variable_x'), $variable->getVariable()));
264
265 $range_min = new ilNumberInputGUI($this->lng->txt('range_min'), 'range_min_' . $variable->getVariable());
266 $range_min->allowDecimals(true);
267 $range_min->setSize(3);
268 $range_min->setRequired(true);
269 $range_min->setValue($variable->getRangeMin());
270
271 $range_max = new ilNumberInputGUI($this->lng->txt('range_max'), 'range_max_' . $variable->getVariable());
272 $range_max->allowDecimals(true);
273 $range_max->setSize(3);
274 $range_max->setRequired(true);
275 $range_max->setValue($variable->getRangeMax());
276
277 $units = new ilSelectInputGUI($this->lng->txt('unit'), 'unit_' . $variable->getVariable());
278 $units->setOptions([0 => $this->lng->txt('no_selection')] + $unit_options);
279 if (is_object($variable->getUnit())) {
280 $units->setValue($variable->getUnit()->getId());
281 }
282
283 $precision = new ilNumberInputGUI($this->lng->txt('precision'), 'precision_' . $variable->getVariable());
284 $precision->setRequired(true);
285 $precision->setSize(3);
286 $precision->setMinValue(0);
287 $precision->setValue($variable->getPrecision());
288 $precision->setInfo($this->lng->txt('fq_precision_info'));
289
290 $intprecision = new ilNumberInputGUI($this->lng->txt('intprecision'), 'intprecision_' . $variable->getVariable());
291 $intprecision->setSize(3);
292 $intprecision->setMinValue(1);
293 $intprecision->setValue($variable->getIntprecision());
294 $intprecision->setInfo($this->lng->txt('intprecision_info'));
295
296 $form->addItem($variable_header);
297 $form->addItem($range_min);
298 $form->addItem($range_max);
299 $form->addItem($units);
300 $form->addItem($precision);
301 $form->addItem($intprecision);
302 }
303 }
304 $quest_vars = [];
305 $result_vars = [];
306 $results = $this->object->getResults();
307 if ($results !== []) {
309 $num_r1 = (int) substr($r1->getResult(), 2);
310 $num_r2 = (int) substr($r2->getResult(), 2);
311 if ($num_r1 > $num_r2) {
312 return 1;
313 } elseif ($num_r1 < $num_r2) {
314 return -1;
315 }
316
317 return 0;
318 });
319
320 foreach ($results as $result) {
321 $result_header = new ilFormSectionHeaderGUI();
322 $result_header->setTitle(sprintf($this->lng->txt('result_x'), $result->getResult()));
323
324 $formula = new ilTextInputGUI($this->lng->txt('formula'), 'formula_' . $result->getResult());
325 $formula->setInfo($this->lng->txt('fq_formula_desc'));
326 $formula->setRequired(true);
327 $formula->setSize(50);
328 $formula->setValue($result->getFormula());
329 $formula->setSuffix(' = ' . $result->getResult());
330
331 if (
332 $suggest_range_for_result !== null &&
333 $suggest_range_for_result === $result->getResult() &&
334 strlen($result->substituteFormula($variables, $results))
335 ) {
336 $result->suggestRange($variables, $results);
337 }
338
339 $range_min = new ilNumberInputGUI($this->lng->txt('range_min'), 'range_min_' . $result->getResult());
340 $range_min->allowDecimals(true);
341 $range_min->setSize(3);
342 $range_min->setRequired(true);
343 $range_min->setValue($result->getRangeMin());
344
345 $range_max = new ilNumberInputGUI($this->lng->txt('range_max'), 'range_max_' . $result->getResult());
346 $range_max->allowDecimals(true);
347 $range_max->setSize(3);
348 $range_max->setRequired(true);
349 $range_max->setValue($result->getRangeMax());
350
351 $matches = [];
352
353 $precision = new ilNumberInputGUI($this->lng->txt('precision'), 'precision_' . $result->getResult());
354 $precision->setRequired(true);
355 $precision->setSize(3);
356 $precision->setMinValue(0);
357 $precision->setInfo($this->lng->txt('fq_precision_info'));
358 $precision->setValue($result->getPrecision());
359
360 $tolerance = new ilNumberInputGUI($this->lng->txt('tolerance'), 'tolerance_' . $result->getResult());
361 $tolerance->setSize(3);
362 $tolerance->setMinValue(0);
363 $tolerance->setMaxValue(100);
364 $tolerance->allowDecimals(true);
365 $tolerance->setInfo($this->lng->txt('tolerance_info'));
366 $tolerance->setValue($result->getTolerance());
367
368 $suggest_range_button = new ilCustomInputGUI('', '');
369 $suggest_range_button->setHtml(
370 $this->ui_renderer->render(
371 $this->ui_factory->button()->standard(
372 $this->lng->txt('suggest_range'),
373 ''
374 )->withAdditionalOnLoadCode(
375 $this->getSuggestRangeOnLoadCode($result->getResult())
376 )
377 )
378 );
379
380 $sel_result_units = new ilSelectInputGUI($this->lng->txt('unit'), 'unit_' . $result->getResult());
381 $sel_result_units->setOptions([0 => $this->lng->txt('no_selection')] + $unit_options);
382 $sel_result_units->setInfo($this->lng->txt('result_unit_info'));
383 if (is_object($result->getUnit())) {
384 $sel_result_units->setValue($result->getUnit()->getId());
385 }
386
387 $mc_result_units = new ilMultiSelectInputGUI($this->lng->txt('result_units'), 'units_' . $result->getResult());
388 $mc_result_units->setOptions($unit_options);
389 $mc_result_units->setInfo($this->lng->txt('result_units_info'));
390 $selectedvalues = [];
391 foreach ($unit_options as $unit_id => $txt) {
392 if ($this->hasResultUnit($result, $unit_id, $result_units)) {
393 $selectedvalues[] = $unit_id;
394 }
395 }
396 $mc_result_units->setValue($selectedvalues);
397
398 $result_type = new ilRadioGroupInputGUI($this->lng->txt('result_type_selection'), 'result_type_' . $result->getResult());
399 $result_type->setRequired(true);
400
401 $no_type = new ilRadioOption($this->lng->txt('no_result_type'), '0');
402 $no_type->setInfo($this->lng->txt('fq_no_restriction_info'));
403
404 $result_dec = new ilRadioOption($this->lng->txt('result_dec'), '1');
405 $result_dec->setInfo($this->lng->txt('result_dec_info'));
406
407 $result_frac = new ilRadioOption($this->lng->txt('result_frac'), '2');
408 $result_frac->setInfo($this->lng->txt('result_frac_info'));
409
410 $result_co_frac = new ilRadioOption($this->lng->txt('result_co_frac'), '3');
411 $result_co_frac->setInfo($this->lng->txt('result_co_frac_info'));
412
413 $result_type->addOption($no_type);
414 $result_type->addOption($result_dec);
415 $result_type->addOption($result_frac);
416 $result_type->addOption($result_co_frac);
417 $result_type->setValue(strlen($result->getResultType()) ? $result->getResultType() : 0);
418
419 $points = new ilNumberInputGUI($this->lng->txt('points'), 'points_' . $result->getResult());
420 $points->allowDecimals(true);
421 $points->setRequired(true);
422 $points->setSize(3);
423 $points->setMinValue(0);
424 $points->setValue(strlen($result->getPoints()) ? $result->getPoints() : 1);
425
426 $rating_type = new ilCheckboxInputGUI($this->lng->txt('advanced_rating'), 'rating_advanced_' . $result->getResult());
427 $rating_type->setValue(1);
428 $rating_type->setInfo($this->lng->txt('advanced_rating_info'));
429
430 if (!$save) {
431 $advanced_rating = $this->canUseAdvancedRating($result);
432 if (!$advanced_rating) {
433 $rating_type->setDisabled(true);
434 $rating_type->setChecked(false);
435 } else {
436 $rating_type->setChecked(strlen($result->getRatingSimple()) && $result->getRatingSimple() ? false : true);
437 }
438 }
439
440 $sign = new ilNumberInputGUI($this->lng->txt('rating_sign'), 'rating_sign_' . $result->getResult());
441 $sign->setRequired(true);
442 $sign->setSize(3);
443 $sign->setMinValue(0);
444 $sign->setValue($result->getRatingSign());
445 $rating_type->addSubItem($sign);
446
447 $value = new ilNumberInputGUI($this->lng->txt('rating_value'), 'rating_value_' . $result->getResult());
448 $value->setRequired(true);
449 $value->setSize(3);
450 $value->setMinValue(0);
451 $value->setValue($result->getRatingValue());
452 $rating_type->addSubItem($value);
453
454 $unit = new ilNumberInputGUI($this->lng->txt('rating_unit'), 'rating_unit_' . $result->getResult());
455 $unit->setRequired(true);
456 $unit->setSize(3);
457 $unit->setMinValue(0);
458 $unit->setValue($result->getRatingUnit());
459 $rating_type->addSubItem($unit);
460
461 $info_text = new ilNonEditableValueGUI($this->lng->txt('additional_rating_info'));
462 $rating_type->addSubItem($info_text);
463
464 $form->addItem($result_header);
465 $form->addItem($formula);
466 $form->addItem($range_min);
467 $form->addItem($range_max);
468 $form->addItem($suggest_range_button);
469 $form->addItem($precision);
470 $form->addItem($tolerance);
471 $form->addItem($sel_result_units);
472 $form->addItem($mc_result_units);
473 $form->addItem($result_type);
474 $form->addItem($points);
475 $form->addItem($rating_type);
476 }
477
478 $defined_result_vars = [];
479
480 $defined_result_res = [];
481
482 foreach ($variables as $key => $object) {
483 $quest_vars[$key] = $key;
484 }
485
486 foreach ($results as $key => $object) {
487 $result_vars[$key] = $key;
488 }
489
490 foreach ($results as $tmp_result) {
494 $formula = $tmp_result->getFormula() ?? '';
495
496 preg_match_all("/([$][v][0-9]*)/", $formula, $form_vars);
497 preg_match_all("/([$][r][0-9]*)/", $formula, $form_res);
498 foreach ($form_vars[0] as $res_var) {
499 $defined_result_vars[$res_var] = $res_var;
500 }
501
502 foreach ($form_res[0] as $res_res) {
503 $defined_result_res[$res_res] = $res_res;
504 }
505 }
506 }
507
508 $result_has_undefined_vars = [];
509 $question_has_unused_vars = [];
510 $result_has_undefined_res = [];
511
512 if (is_array($quest_vars) && count($quest_vars) > 0) {
513 $result_has_undefined_vars = array_diff($defined_result_vars, $quest_vars);
514 $question_has_unused_vars = array_diff($quest_vars, $defined_result_vars);
515 }
516
517 if (is_array($result_vars) && count($result_vars) > 0) {
518 $result_has_undefined_res = array_diff($defined_result_res, $result_vars);
519 }
520 $error_message = '';
521 $checked = true;
522 if ($result_has_undefined_vars !== [] || $question_has_unused_vars !== []) {
523 if (count($result_has_undefined_vars) > 0) {
524 $error_message .= $this->lng->txt("res_contains_undef_var") . '<br>';
525 }
526 if (count($question_has_unused_vars) > 0) {
527 $error_message .= $this->lng->txt("que_contains_unused_var") . '<br>';
528 }
529
530 $checked = false;
531 if ($save) {
532 $this->tpl->setOnScreenMessage('failure', $error_message);
533 }
534 }
535
536 if (is_array($result_has_undefined_res) && count($result_has_undefined_res) > 0) {
537 $error_message .= $this->lng->txt("res_contains_undef_res") . '<br>';
538 $checked = false;
539 }
540
541 if ($save && !$checked) {
542 $this->tpl->setOnScreenMessage('failure', $error_message);
543 }
544
545 $this->populateTaxonomyFormSection($form);
546
547 $form->addCommandButton('parseQuestion', $this->lng->txt('parseQuestion'));
548 $form->addCommandButton('saveReturn', $this->lng->txt('save_return'));
549 $form->addCommandButton('save', $this->lng->txt('save'));
550
551 $errors = !$checked;
552
553 if ($save) {
554 $found_vars = [];
555 $found_results = [];
556 foreach ($this->request_data_collector->getParsedBody() as $key => $value) {
557 if (preg_match("/^unit_(\\\$v\d+)$/", $key, $matches)) {
558 array_push($found_vars, $matches[1]);
559 }
560 if (preg_match("/^unit_(\\\$r\d+)$/", $key, $matches)) {
561 array_push($found_results, $matches[1]);
562 }
563 }
564
565
566 foreach ($this->request_data_collector->getParsedBody() as $key => $value) {
567 $item = $form->getItemByPostVar($key);
568 if ($item !== null) {
569 switch (get_class($item)) {
570 case 'ilDurationInputGUI':
571 $item->setHours($value['hh']);
572 $item->setMinutes($value['mm']);
573 $item->setSeconds($value['ss']);
574 break;
575 default:
576 $item->setValue($value);
577 }
578 }
579 }
580
581 $check = array_merge($found_vars, $found_results);
582 foreach ((array) $form->getItems() as $item) {
583 $postvar = $item->getPostVar();
584 if (preg_match("/_\\\$[r|v]\d+/", $postvar, $matches)) {
585 $k = substr(array_shift($matches), 1);
586 if (!in_array($k, $check)) {
587 $form->removeItemByPostVar($postvar);
588 }
589 }
590 }
591 $variables = array_filter($variables, fn($k, $v) => in_array($v, $check), ARRAY_FILTER_USE_BOTH);
592 $results = array_filter($results, fn($k, $v) => in_array($k, $check), ARRAY_FILTER_USE_BOTH);
593
594 $errors = !$form->checkInput();
595
596 $custom_errors = false;
598 foreach ($variables as $variable) {
599 $min_range = $form->getItemByPostVar('range_min_' . $variable->getVariable());
600 $max_range = $form->getItemByPostVar('range_max_' . $variable->getVariable());
601 $min_range_value = $min_range?->getValue();
602 $max_range_value = $max_range?->getValue();
603
604 if ($min_range_value === null || $max_range_value === null) {
605 $custom_errors = true;
606 continue;
607 }
608
609 if ($min_range_value > $max_range_value) {
610 $min_range?->setAlert($this->lng->txt('err_range'));
611 $max_range?->setAlert($this->lng->txt('err_range'));
612 $custom_errors = true;
613 }
614
615 $decimal_spots = $form->getItemByPostVar('precision_' . $variable->getVariable());
616 $int_precision = $form->getItemByPostVar('intprecision_' . $variable->getVariable());
617 if (
618 $decimal_spots instanceof ilFormPropertyGUI
619 && $decimal_spots->getValue() === 0.0
620 && !$variable->isIntPrecisionValid($int_precision?->getValue(), $min_range_value, $max_range_value)
621 ) {
622 $int_precision?->setAlert($this->lng->txt('err_division'));
623 $custom_errors = true;
624 }
625 }
626
627 if ($results !== []) {
628 foreach ($results as $result) {
632 $min_range = $form->getItemByPostVar('range_min_' . $result->getResult());
633 $max_range = $form->getItemByPostVar('range_max_' . $result->getResult());
634 if ($min_range->getValue() > $max_range->getValue()) {
635 $min_range->setAlert($this->lng->txt('err_range'));
636 $max_range->setAlert($this->lng->txt('err_range'));
637 $custom_errors = true;
638 }
639
640
641 $formula = $form->getItemByPostVar('formula_' . $result->getResult());
642 if (strpos($formula->getValue(), $result->getResult()) !== false) {
643 $formula->setAlert($this->lng->txt('errRecursionInResult'));
644 $custom_errors = true;
645 }
646
647 $result_unit = $form->getItemByPostVar('unit_' . $result->getResult());
648 $rating_advanced = $form->getItemByPostVar('rating_advanced_' . $result->getResult());
649 if (((int) $result_unit->getValue() <= 0) && $rating_advanced->getChecked()) {
650 unset($_POST['rating_advanced_' . $result->getResult()]); // TODO
651 $rating_advanced->setDisabled(true);
652 $rating_advanced->setChecked(false);
653 $rating_advanced->setAlert($this->lng->txt('err_rating_advanced_not_allowed'));
654 $custom_errors = true;
655 } elseif ($rating_advanced->getChecked()) {
656 $rating_sign = $form->getItemByPostVar('rating_sign_' . $result->getResult());
657 $rating_value = $form->getItemByPostVar('rating_value_' . $result->getResult());
658 $rating_unit = $form->getItemByPostVar('rating_unit_' . $result->getResult());
659
660 $percentage = $rating_sign->getValue() + $rating_value->getValue() + $rating_unit->getValue();
661 if ($percentage != 100) {
662 $rating_advanced->setAlert($this->lng->txt('err_wrong_rating_advanced'));
663 $custom_errors = true;
664 }
665 }
666
667 preg_match_all("/([$][v][0-9]*)/", $formula->getValue(), $form_vars);
668 $result_has_undefined_vars = array_diff($form_vars[0], $found_vars);
669 if (count($result_has_undefined_vars)) {
670 $errors = true;
671 $this->tpl->setOnScreenMessage('info', $this->lng->txt('res_contains_undef_var'));
672 }
673 }
674 }
675
676 if ($custom_errors && !$errors) {
677 $errors = true;
678 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid'));
679 }
680 foreach ($this->request_data_collector->getParsedBody() as $key => $value) {
681 $item = $form->getItemByPostVar($key);
682 if ($item !== null) {
683 switch (get_class($item)) {
684 case 'ilDurationInputGUI':
685 $item->setHours($value['hh']);
686 $item->setMinutes($value['mm']);
687 $item->setSeconds($value['ss']);
688 break;
689 default:
690 $item->setValue($value);
691 }
692 }
693 } // again, because checkInput now performs the whole stripSlashes handling and we need this if we don't want to have duplication of backslashes
694 if ($errors) {
695 $checkonly = false;
696 }
697 }
698
699 if (!$checkonly) {
700 $this->renderEditForm($form);
701 }
702 return $errors;
703 }
704
705 private function getSuggestRangeOnLoadCode(string $result): Closure
706 {
707 return static function ($id) use ($result): string {
708 return "document.getElementById('$id').addEventListener('click', "
709 . '(e) => {'
710 . ' e.target.setAttribute("name", "cmd[suggestRange]");'
711 . ' let input = document.createElement("input");'
712 . ' input.type = "hidden";'
713 . ' input.name = "suggest_range_for";'
714 . " input.value = '$result';"
715 . ' e.target.form.appendChild(input);'
716 . ' e.target.form.requestSubmit(e.target);'
717 . '});';
718 };
719 }
720
721 private function hasResultUnit($result, $unit_id, $resultunits): bool
722 {
723 if (array_key_exists($result->getResult(), $resultunits)) {
724 if (array_key_exists($unit_id, $resultunits[$result->getResult()])) {
725 return true;
726 }
727 }
728 return false;
729 }
730
738 private function canUseAdvancedRating($result): bool
739 {
740 $resultunit = $result->getUnit();
741
742 /*
743 * if there is a result-unit (unit selectbox) selected it is possible to use advanced rating
744 * if there is no result-unit selected it is NOT possible to use advanced rating, because there is no
745 * definition if the result-value or the unit-value should be the correct solution!!
746 *
747 */
748 if (is_object($resultunit)) {
749 return true;
750 } else {
751 return false;
752 }
753 }
754
755 public function parseQuestion(): void
756 {
757 $this->setAdditionalContentEditingModeFromPost();
758 $this->writePostData();
759 $this->addSaveOnEnterOnLoadCode();
760 $this->setTestSpecificProperties();
761 $this->editQuestion();
762 }
763
767 public function checkInput(): bool
768 {
769 $title = $this->request_data_collector->string('title');
770 $author = $this->request_data_collector->string('author');
771 $question = $this->request_data_collector->string('question');
772
773 if (empty($title) || empty($author) || empty($question)) {
774 $this->addErrorMessage($this->lng->txt('fill_out_all_required_fields'));
775 return false;
776 }
777
778 return true;
779 }
780
781 public function getSolutionOutput(
782 int $active_id,
783 ?int $pass = null,
784 bool $graphical_output = false,
785 bool $result_output = false,
786 bool $show_question_only = true,
787 bool $show_feedback = false,
788 bool $show_correct_solution = false,
789 bool $show_manual_scoring = false,
790 bool $show_question_text = true,
791 bool $show_inline_feedback = true
792 ): string {
793 $user_solution = [];
794 if ($pass !== null) {
795 $user_solution = $this->object->getVariableSolutionValuesForPass($active_id, $pass);
796 } else {
797 $user_solution = array_reduce(
798 $this->object->fetchAllVariables($this->object->getQuestion()),
799 static function (array $c, assFormulaQuestionVariable $v): array {
800 $c[$v->getVariable()] = $v->getVariable();
801 return $c;
802 },
803 []
804 );
805 }
806 if (($active_id > 0) && (!$show_correct_solution)) {
807 $user_solution['active_id'] = $active_id;
808 $user_solution['pass'] = $pass;
809 $solutions = $this->object->getSolutionValues($active_id, $pass);
810 foreach ($solutions as $solution_value) {
811 if (preg_match('/^(\$v\d+)$/', $solution_value['value1'], $matches)) {
812 $user_solution[$matches[1]] = $solution_value['value2'];
813 } elseif (preg_match('/^(\$r\d+)$/', $solution_value['value1'], $matches)) {
814 if (!array_key_exists($matches[1], $user_solution)) {
815 $user_solution[$matches[1]] = [];
816 }
817 $user_solution[$matches[1]]["value"] = $solution_value['value2'];
818 } elseif (preg_match('/^(\$r\d+)_unit$/', $solution_value['value1'], $matches)) {
819 if (!array_key_exists($matches[1], $user_solution)) {
820 $user_solution[$matches[1]] = [];
821 }
822 $user_solution[$matches[1]]['unit'] = $solution_value['value2'];
823 }
824 }
825 } elseif ($active_id !== 0) {
826 $user_solution = $this->object->getBestSolution($this->object->getSolutionValues($active_id, $pass));
827 } elseif (is_object($this->getPreviewSession())) {
828 $solutionValues = [];
829 $participants_solution = $this->getPreviewSession()->getParticipantsSolution();
830 if (is_array($participants_solution)) {
831 foreach ($participants_solution as $val1 => $val2) {
832 $solutionValues[] = ['value1' => $val1, 'value2' => $val2];
833 }
834 }
835 $user_solution = $this->object->getBestSolution($solutionValues);
836 }
837
838 return $this->renderSolutionOutput(
839 $user_solution,
840 $active_id,
841 $pass,
842 $graphical_output,
843 $result_output,
844 $show_question_only,
845 $show_feedback,
846 $show_correct_solution,
847 $show_manual_scoring,
848 $show_question_text,
849 false,
850 $show_inline_feedback
851 );
852 }
853
854 public function renderSolutionOutput(
855 mixed $user_solutions,
856 int $active_id,
857 ?int $pass,
858 bool $graphical_output = false,
859 bool $result_output = false,
860 bool $show_question_only = true,
861 bool $show_feedback = false,
862 bool $show_correct_solution = false,
863 bool $show_manual_scoring = false,
864 bool $show_question_text = true,
865 bool $show_autosave_title = false,
866 bool $show_inline_feedback = false,
867 ): ?string {
868 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output_solution.html", true, true, 'components/ILIAS/TestQuestionPool');
869 $correctness_icons = [
870 'correct' => $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK),
871 'not_correct' => $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK)
872 ];
873 $questiontext = $this->object->substituteVariables($user_solutions, $graphical_output, true, $result_output, $correctness_icons);
874 $template->setVariable("QUESTIONTEXT", $this->renderLatex(
876 ));
877 $questionoutput = $template->get();
878 $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "components/ILIAS/TestQuestionPool");
879 $feedback = ($show_feedback) ? $this->getGenericFeedbackOutput((int) $active_id, $pass) : "";
880 if (strlen($feedback)) {
881 $cssClass = (
882 $this->hasCorrectSolution($active_id, $pass) ?
884 );
885 $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
886 $solutiontemplate->setVariable("FEEDBACK", $this->renderLatex(
888 ));
889 }
890 $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
891
892 $solutionoutput = $solutiontemplate->get();
893 if (!$show_question_only) {
894 $solutionoutput = $this->getILIASPage($solutionoutput);
895 }
896 return $solutionoutput;
897 }
898
899 public function getPreview(
900 bool $show_question_only = false,
901 bool $show_inline_feedback = false
902 ): string {
903 $user_solution = [];
904 if (is_object($this->getPreviewSession())) {
905 $solutions = $this->getPreviewSession()->getParticipantsSolution() ?? [];
906 foreach ($solutions as $val1 => $val2) {
907 if (preg_match('/^(\$v\d+)$/', $val1, $matches)) {
908 $user_solution[$matches[1]] = $val2;
909 } elseif (preg_match('/^(\$r\d+)$/', $val1, $matches)) {
910 if (!array_key_exists($matches[1], $user_solution)) {
911 $user_solution[$matches[1]] = [];
912 }
913 $user_solution[$matches[1]]['value'] = $val2;
914 } elseif (preg_match('/^(\$r\d+)_unit$/', $val1, $matches)) {
915 if (!array_key_exists($matches[1], $user_solution)) {
916 $user_solution[$matches[1]] = [];
917 }
918 $user_solution[$matches[1]]['unit'] = $val2;
919 }
920
921 if (preg_match('/^(\$r\d+)/', $val1, $matches) && !isset($user_solution[$matches[1]]['result_type'])) {
922 $user_solution[$matches[1]]['result_type'] = assFormulaQuestionResult::getResultTypeByQstId($this->object->getId(), $val1);
923 }
924 }
925 }
926
927 if (!$this->object->hasRequiredVariableSolutionValues($user_solution)) {
928 $user_solution = $this->object->getInitialVariableSolutionValues();
929
930 if (is_object($this->getPreviewSession())) {
931 $this->getPreviewSession()->setParticipantsSolution($user_solution);
932 }
933 }
934
935 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output.html", true, true, 'components/ILIAS/TestQuestionPool');
936 if (is_object($this->getPreviewSession())) {
937 $questiontext = $this->object->substituteVariables($user_solution);
938 } else {
939 $questiontext = $this->object->substituteVariables([]);
940 }
941 $template->setVariable("QUESTIONTEXT", $this->renderLatex(
943 ));
944 $questionoutput = $template->get();
945 if (!$show_question_only) {
946 // get page object output
947 $questionoutput = $this->getILIASPage($questionoutput);
948 }
949 return $questionoutput;
950 }
951
952 public function getTestOutput(
953 int $active_id,
954 int $pass,
955 bool $is_question_postponed = false,
956 array|bool $user_post_solutions = false,
957 bool $show_specific_inline_feedback = false
958 ): string {
959 $this->tpl->setOnScreenMessage('info', $this->lng->txt('enter_valid_values'));
960 // get the solution of the user for the active pass or from the last pass if allowed
961 $user_solution = [];
962 if ($active_id) {
963 $solutions = $this->object->getTestOutputSolutions($active_id, $pass);
964 $actualPassIndex = null;
965 if ($this->object->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
966 $actualPassIndex = ilObjTest::_getPass($active_id);
967 }
968 foreach ($solutions as $idx => $solution_value) {
969 if (preg_match('/^(\$v\d+)$/', $solution_value['value1'], $matches)) {
970 if ($this->object->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
971 $this->object->saveCurrentSolution($active_id, $actualPassIndex, $matches[1], $solution_value['value2'], true);
972 }
973 $user_solution[$matches[1]] = $solution_value['value2'];
974 } elseif (preg_match('/^(\$r\d+)$/', $solution_value['value1'], $matches)) {
975 if (!array_key_exists($matches[1], $user_solution)) {
976 $user_solution[$matches[1]] = [];
977 }
978 $user_solution[$matches[1]]['value'] = $solution_value['value2'];
979 } elseif (preg_match('/^(\$r\d+)_unit$/', $solution_value['value1'], $matches)) {
980 if (!array_key_exists($matches[1], $user_solution)) {
981 $user_solution[$matches[1]] = [];
982 }
983 $user_solution[$matches[1]]['unit'] = $solution_value['value2'];
984 }
985 if (preg_match('/^(\$r\d+)/', $solution_value['value1'], $matches) && !isset($user_solution[$matches[1]]['result_type'])) {
986 $user_solution[$matches[1]]['result_type'] = assFormulaQuestionResult::getResultTypeByQstId($this->object->getId(), $solution_value['value1']);
987 }
988 }
989 }
990
991 // fau: testNav - take question variables always from authorized solution because they are saved with this flag, even if an authorized solution is not saved
992 $solutions = $this->object->getSolutionValues($active_id, $pass, true);
993 foreach ($solutions as $idx => $solution_value) {
994 if (preg_match('/^(\$v\d+)$/', $solution_value['value1'], $matches)) {
995 $user_solution[$matches[1]] = $solution_value['value2'];
996 }
997 }
998
999 if ($user_solution === []) {
1000 $user_solution = $this->object->getVariableSolutionValuesForPass($active_id, $pass);
1001 }
1002
1003 // generate the question output
1004 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output.html", true, true, 'components/ILIAS/TestQuestionPool');
1005
1006 $questiontext = $this->object->substituteVariables($user_solution);
1007
1008 $template->setVariable("QUESTIONTEXT", $this->renderLatex(
1010 ));
1011
1012 $questionoutput = $template->get();
1013 $pageoutput = $this->outQuestionPage("", $is_question_postponed, $active_id, $questionoutput);
1014 return $pageoutput;
1015 }
1016
1017 public function getSpecificFeedbackOutput(array $userSolution): string
1018 {
1019 return '';
1020 }
1021}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$lifecycle
$check
Definition: buildRTE.php:81
setVariable($variable, $value='')
Sets a variable value.
Definition: IT.php:544
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...
getPreview(bool $show_question_only=false, bool $show_inline_feedback=false)
canUseAdvancedRating($result)
Check if advanced rating can be used for a result.
getSpecificFeedbackOutput(array $userSolution)
Returns the answer specific feedback for the question.
hasResultUnit($result, $unit_id, $resultunits)
setQuestionSpecificTabs(ilTabsGUI $ilTabs)
getTestOutput(int $active_id, int $pass, bool $is_question_postponed=false, array|bool $user_post_solutions=false, bool $show_specific_inline_feedback=false)
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)
writePostData(bool $always=false)
{Evaluates a posted edit form and writes the form data in the question object.integer A positive valu...
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,)
__construct($id=-1)
assFormulaQuestionGUI constructor The constructor takes possible arguments an creates an instance of ...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getResultTypeByQstId($a_qst_id, $a_result)
Class for single choice questions assFormulaQuestion is a class for single choice questions.
populateTaxonomyFormSection(ilPropertyFormGUI $form)
editQuestion(bool $checkonly=false, ?bool $is_save_cmd=null)
addBasicQuestionFormProperties(ilPropertyFormGUI $form)
renderEditForm(ilPropertyFormGUI $form)
addErrorMessage(string $errormessage)
This class represents a checkbox property in a property form.
This class represents a custom property in a property form.
This class represents a property in a property form.
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,...
This class represents a multi selection list property in a property form.
This class represents a non editable value in a property form.
This class represents a number property in a property form.
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
This class represents a property form user interface.
This class represents a property in a property form.
This class represents an option in a radio group.
This class represents a selection list property in a property form.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
addTarget(string $a_text, string $a_link, $a_cmd="", $a_cmdClass="", string $a_frame="", bool $a_activate=false, bool $a_dir_text=false)
special template class to simplify handling of ITX/PEAR
This class represents a text property in a property form.
$c
Definition: deliver.php:25
An entity that renders components to a string output.
Definition: Renderer.php:31
$_POST['cmd']
Definition: lti.php:27
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$results
global $DIC
Definition: shib_login.php:26