ILIAS  trunk Revision v12.0_alpha-377-g3641b37b9db
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 ($decimal_spots instanceof ilFormPropertyGUI && $decimal_spots->getValue() === 0) {
618 $txt = !$variable->isIntPrecisionValid($int_precision?->getValue(), $min_range_value, $max_range_value)
619 ? 'err_divider_too_big_specific'
620 : 'err_division';
621 $int_precision?->setAlert($this->lng->txt($txt));
622 $custom_errors = true;
623 }
624 }
625
626 if ($results !== []) {
627 foreach ($results as $result) {
631 $min_range = $form->getItemByPostVar('range_min_' . $result->getResult());
632 $max_range = $form->getItemByPostVar('range_max_' . $result->getResult());
633 if ($min_range->getValue() > $max_range->getValue()) {
634 $min_range->setAlert($this->lng->txt('err_range'));
635 $max_range->setAlert($this->lng->txt('err_range'));
636 $custom_errors = true;
637 }
638
639
640 $formula = $form->getItemByPostVar('formula_' . $result->getResult());
641 if (strpos($formula->getValue(), $result->getResult()) !== false) {
642 $formula->setAlert($this->lng->txt('errRecursionInResult'));
643 $custom_errors = true;
644 }
645
646 $result_unit = $form->getItemByPostVar('unit_' . $result->getResult());
647 $rating_advanced = $form->getItemByPostVar('rating_advanced_' . $result->getResult());
648 if (((int) $result_unit->getValue() <= 0) && $rating_advanced->getChecked()) {
649 unset($_POST['rating_advanced_' . $result->getResult()]); // TODO
650 $rating_advanced->setDisabled(true);
651 $rating_advanced->setChecked(false);
652 $rating_advanced->setAlert($this->lng->txt('err_rating_advanced_not_allowed'));
653 $custom_errors = true;
654 } elseif ($rating_advanced->getChecked()) {
655 $rating_sign = $form->getItemByPostVar('rating_sign_' . $result->getResult());
656 $rating_value = $form->getItemByPostVar('rating_value_' . $result->getResult());
657 $rating_unit = $form->getItemByPostVar('rating_unit_' . $result->getResult());
658
659 $percentage = $rating_sign->getValue() + $rating_value->getValue() + $rating_unit->getValue();
660 if ($percentage != 100) {
661 $rating_advanced->setAlert($this->lng->txt('err_wrong_rating_advanced'));
662 $custom_errors = true;
663 }
664 }
665
666 preg_match_all("/([$][v][0-9]*)/", $formula->getValue(), $form_vars);
667 $result_has_undefined_vars = array_diff($form_vars[0], $found_vars);
668 if (count($result_has_undefined_vars)) {
669 $errors = true;
670 $this->tpl->setOnScreenMessage('info', $this->lng->txt('res_contains_undef_var'));
671 }
672 }
673 }
674
675 if ($custom_errors && !$errors) {
676 $errors = true;
677 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('form_input_not_valid'));
678 }
679 foreach ($this->request_data_collector->getParsedBody() as $key => $value) {
680 $item = $form->getItemByPostVar($key);
681 if ($item !== null) {
682 switch (get_class($item)) {
683 case 'ilDurationInputGUI':
684 $item->setHours($value['hh']);
685 $item->setMinutes($value['mm']);
686 $item->setSeconds($value['ss']);
687 break;
688 default:
689 $item->setValue($value);
690 }
691 }
692 } // again, because checkInput now performs the whole stripSlashes handling and we need this if we don't want to have duplication of backslashes
693 if ($errors) {
694 $checkonly = false;
695 }
696 }
697
698 if (!$checkonly) {
699 $this->renderEditForm($form);
700 }
701 return $errors;
702 }
703
704 private function getSuggestRangeOnLoadCode(string $result): Closure
705 {
706 return static function ($id) use ($result): string {
707 return "document.getElementById('$id').addEventListener('click', "
708 . '(e) => {'
709 . ' e.target.setAttribute("name", "cmd[suggestRange]");'
710 . ' let input = document.createElement("input");'
711 . ' input.type = "hidden";'
712 . ' input.name = "suggest_range_for";'
713 . " input.value = '$result';"
714 . ' e.target.form.appendChild(input);'
715 . ' e.target.form.requestSubmit(e.target);'
716 . '});';
717 };
718 }
719
720 private function hasResultUnit($result, $unit_id, $resultunits): bool
721 {
722 if (array_key_exists($result->getResult(), $resultunits)) {
723 if (array_key_exists($unit_id, $resultunits[$result->getResult()])) {
724 return true;
725 }
726 }
727 return false;
728 }
729
737 private function canUseAdvancedRating($result): bool
738 {
739 $resultunit = $result->getUnit();
740
741 /*
742 * if there is a result-unit (unit selectbox) selected it is possible to use advanced rating
743 * if there is no result-unit selected it is NOT possible to use advanced rating, because there is no
744 * definition if the result-value or the unit-value should be the correct solution!!
745 *
746 */
747 if (is_object($resultunit)) {
748 return true;
749 } else {
750 return false;
751 }
752 }
753
754 public function parseQuestion(): void
755 {
756 $this->setAdditionalContentEditingModeFromPost();
757 $this->writePostData();
758 $this->addSaveOnEnterOnLoadCode();
759 $this->setTestSpecificProperties();
760 $this->editQuestion();
761 }
762
766 public function checkInput(): bool
767 {
768 $title = $this->request_data_collector->string('title');
769 $author = $this->request_data_collector->string('author');
770 $question = $this->request_data_collector->string('question');
771
772 if (empty($title) || empty($author) || empty($question)) {
773 $this->addErrorMessage($this->lng->txt('fill_out_all_required_fields'));
774 return false;
775 }
776
777 return true;
778 }
779
780 public function getSolutionOutput(
781 int $active_id,
782 ?int $pass = null,
783 bool $graphical_output = false,
784 bool $result_output = false,
785 bool $show_question_only = true,
786 bool $show_feedback = false,
787 bool $show_correct_solution = false,
788 bool $show_manual_scoring = false,
789 bool $show_question_text = true,
790 bool $show_inline_feedback = true
791 ): string {
792 $user_solution = [];
793 if ($pass !== null) {
794 $user_solution = $this->object->getVariableSolutionValuesForPass($active_id, $pass);
795 } else {
796 $user_solution = array_reduce(
797 $this->object->fetchAllVariables($this->object->getQuestion()),
798 static function (array $c, assFormulaQuestionVariable $v): array {
799 $c[$v->getVariable()] = $v->getVariable();
800 return $c;
801 },
802 []
803 );
804 }
805 if (($active_id > 0) && (!$show_correct_solution)) {
806 $user_solution['active_id'] = $active_id;
807 $user_solution['pass'] = $pass;
808 $solutions = $this->object->getSolutionValues($active_id, $pass);
809 foreach ($solutions as $solution_value) {
810 if (preg_match('/^(\$v\d+)$/', $solution_value['value1'], $matches)) {
811 $user_solution[$matches[1]] = $solution_value['value2'];
812 } elseif (preg_match('/^(\$r\d+)$/', $solution_value['value1'], $matches)) {
813 if (!array_key_exists($matches[1], $user_solution)) {
814 $user_solution[$matches[1]] = [];
815 }
816 $user_solution[$matches[1]]["value"] = $solution_value['value2'];
817 } elseif (preg_match('/^(\$r\d+)_unit$/', $solution_value['value1'], $matches)) {
818 if (!array_key_exists($matches[1], $user_solution)) {
819 $user_solution[$matches[1]] = [];
820 }
821 $user_solution[$matches[1]]['unit'] = $solution_value['value2'];
822 }
823 }
824 } elseif ($active_id !== 0) {
825 $user_solution = $this->object->getBestSolution($this->object->getSolutionValues($active_id, $pass));
826 } elseif (is_object($this->getPreviewSession())) {
827 $solutionValues = [];
828 $participants_solution = $this->getPreviewSession()->getParticipantsSolution();
829 if (is_array($participants_solution)) {
830 foreach ($participants_solution as $val1 => $val2) {
831 $solutionValues[] = ['value1' => $val1, 'value2' => $val2];
832 }
833 }
834 $user_solution = $this->object->getBestSolution($solutionValues);
835 }
836
837 return $this->renderSolutionOutput(
838 $user_solution,
839 $active_id,
840 $pass,
841 $graphical_output,
842 $result_output,
843 $show_question_only,
844 $show_feedback,
845 $show_correct_solution,
846 $show_manual_scoring,
847 $show_question_text,
848 false,
849 $show_inline_feedback
850 );
851 }
852
853 public function renderSolutionOutput(
854 mixed $user_solutions,
855 int $active_id,
856 ?int $pass,
857 bool $graphical_output = false,
858 bool $result_output = false,
859 bool $show_question_only = true,
860 bool $show_feedback = false,
861 bool $show_correct_solution = false,
862 bool $show_manual_scoring = false,
863 bool $show_question_text = true,
864 bool $show_autosave_title = false,
865 bool $show_inline_feedback = false,
866 ): ?string {
867 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output_solution.html", true, true, 'components/ILIAS/TestQuestionPool');
868 $correctness_icons = [
869 'correct' => $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_OK),
870 'not_correct' => $this->generateCorrectnessIconsForCorrectness(self::CORRECTNESS_NOT_OK)
871 ];
872 $questiontext = $this->object->substituteVariables($user_solutions, $graphical_output, true, $result_output, $correctness_icons);
873 $template->setVariable("QUESTIONTEXT", $this->renderLatex(
875 ));
876 $questionoutput = $template->get();
877 $solutiontemplate = new ilTemplate("tpl.il_as_tst_solution_output.html", true, true, "components/ILIAS/TestQuestionPool");
878 $feedback = ($show_feedback) ? $this->getGenericFeedbackOutput((int) $active_id, $pass) : "";
879 if (strlen($feedback)) {
880 $cssClass = (
881 $this->hasCorrectSolution($active_id, $pass) ?
883 );
884 $solutiontemplate->setVariable("ILC_FB_CSS_CLASS", $cssClass);
885 $solutiontemplate->setVariable("FEEDBACK", $this->renderLatex(
887 ));
888 }
889 $solutiontemplate->setVariable("SOLUTION_OUTPUT", $questionoutput);
890
891 $solutionoutput = $solutiontemplate->get();
892 if (!$show_question_only) {
893 $solutionoutput = $this->getILIASPage($solutionoutput);
894 }
895 return $solutionoutput;
896 }
897
898 public function getPreview(
899 bool $show_question_only = false,
900 bool $show_inline_feedback = false
901 ): string {
902 $user_solution = [];
903 if (is_object($this->getPreviewSession())) {
904 $solutions = $this->getPreviewSession()->getParticipantsSolution() ?? [];
905 foreach ($solutions as $val1 => $val2) {
906 if (preg_match('/^(\$v\d+)$/', $val1, $matches)) {
907 $user_solution[$matches[1]] = $val2;
908 } elseif (preg_match('/^(\$r\d+)$/', $val1, $matches)) {
909 if (!array_key_exists($matches[1], $user_solution)) {
910 $user_solution[$matches[1]] = [];
911 }
912 $user_solution[$matches[1]]['value'] = $val2;
913 } elseif (preg_match('/^(\$r\d+)_unit$/', $val1, $matches)) {
914 if (!array_key_exists($matches[1], $user_solution)) {
915 $user_solution[$matches[1]] = [];
916 }
917 $user_solution[$matches[1]]['unit'] = $val2;
918 }
919
920 if (preg_match('/^(\$r\d+)/', $val1, $matches) && !isset($user_solution[$matches[1]]['result_type'])) {
921 $user_solution[$matches[1]]['result_type'] = assFormulaQuestionResult::getResultTypeByQstId($this->object->getId(), $val1);
922 }
923 }
924 }
925
926 if (!$this->object->hasRequiredVariableSolutionValues($user_solution)) {
927 $user_solution = $this->object->getInitialVariableSolutionValues();
928
929 if (is_object($this->getPreviewSession())) {
930 $this->getPreviewSession()->setParticipantsSolution($user_solution);
931 }
932 }
933
934 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output.html", true, true, 'components/ILIAS/TestQuestionPool');
935 if (is_object($this->getPreviewSession())) {
936 $questiontext = $this->object->substituteVariables($user_solution);
937 } else {
938 $questiontext = $this->object->substituteVariables([]);
939 }
940 $template->setVariable("QUESTIONTEXT", $this->renderLatex(
942 ));
943 $questionoutput = $template->get();
944 if (!$show_question_only) {
945 // get page object output
946 $questionoutput = $this->getILIASPage($questionoutput);
947 }
948 return $questionoutput;
949 }
950
951 public function getTestOutput(
952 int $active_id,
953 int $pass,
954 bool $is_question_postponed = false,
955 array|bool $user_post_solutions = false,
956 bool $show_specific_inline_feedback = false
957 ): string {
958 $this->tpl->setOnScreenMessage('info', $this->lng->txt('enter_valid_values'));
959 // get the solution of the user for the active pass or from the last pass if allowed
960 $user_solution = [];
961 if ($active_id) {
962 $solutions = $this->object->getTestOutputSolutions($active_id, $pass);
963 $actualPassIndex = null;
964 if ($this->object->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
965 $actualPassIndex = ilObjTest::_getPass($active_id);
966 }
967 foreach ($solutions as $idx => $solution_value) {
968 if (preg_match('/^(\$v\d+)$/', $solution_value['value1'], $matches)) {
969 if ($this->object->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
970 $this->object->saveCurrentSolution($active_id, $actualPassIndex, $matches[1], $solution_value['value2'], true);
971 }
972 $user_solution[$matches[1]] = $solution_value['value2'];
973 } elseif (preg_match('/^(\$r\d+)$/', $solution_value['value1'], $matches)) {
974 if (!array_key_exists($matches[1], $user_solution)) {
975 $user_solution[$matches[1]] = [];
976 }
977 $user_solution[$matches[1]]['value'] = $solution_value['value2'];
978 } elseif (preg_match('/^(\$r\d+)_unit$/', $solution_value['value1'], $matches)) {
979 if (!array_key_exists($matches[1], $user_solution)) {
980 $user_solution[$matches[1]] = [];
981 }
982 $user_solution[$matches[1]][['unit']] = $solution_value['value2'];
983 }
984 if (preg_match('/^(\$r\d+)/', $solution_value['value1'], $matches) && !isset($user_solution[$matches[1]]['result_type'])) {
985 $user_solution[$matches[1]]['result_type'] = assFormulaQuestionResult::getResultTypeByQstId($this->object->getId(), $solution_value['value1']);
986 }
987 }
988 }
989
990 // fau: testNav - take question variables always from authorized solution because they are saved with this flag, even if an authorized solution is not saved
991 $solutions = $this->object->getSolutionValues($active_id, $pass, true);
992 foreach ($solutions as $idx => $solution_value) {
993 if (preg_match('/^(\$v\d+)$/', $solution_value['value1'], $matches)) {
994 $user_solution[$matches[1]] = $solution_value['value2'];
995 }
996 }
997
998 if ($user_solution === []) {
999 $user_solution = $this->object->getVariableSolutionValuesForPass($active_id, $pass);
1000 }
1001
1002 // generate the question output
1003 $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output.html", true, true, 'components/ILIAS/TestQuestionPool');
1004
1005 $questiontext = $this->object->substituteVariables($user_solution);
1006
1007 $template->setVariable("QUESTIONTEXT", $this->renderLatex(
1009 ));
1010
1011 $questionoutput = $template->get();
1012 $pageoutput = $this->outQuestionPage("", $is_question_postponed, $active_id, $questionoutput);
1013 return $pageoutput;
1014 }
1015
1016 public function getSpecificFeedbackOutput(array $userSolution): string
1017 {
1018 return '';
1019 }
1020}
$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
getValue()
Get the value that is displayed in the input client side.
Definition: Group.php:49
$results
global $DIC
Definition: shib_login.php:26