ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
SettingsQuestionBehaviour.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
28use ILIAS\Refinery\Factory as Refinery;
32
34{
35 private const DEFAULT_AUTOSAVE_INTERVAL = 30000;
36
37 public const ANSWER_FIXATION_NONE = 'none';
38 public const ANSWER_FIXATION_ON_IFB_OR_FUQST = 'ifb_or_fuqst';
39
40 public function __construct(
41 int $test_id,
42 protected int $question_title_output_mode,
43 protected bool $autosave_enabled,
44 protected int $autosave_interval,
45 protected bool $shuffle_questions,
46 protected bool $instant_feedback_points_enabled,
47 protected bool $instant_feedback_generic_enabled,
48 protected bool $instant_feedback_specific_enabled,
49 protected bool $instant_feedback_solution_enabled,
50 protected bool $force_instant_feedback_on_next_question,
51 protected bool $lock_answer_on_instant_feedback,
52 protected bool $lock_answer_on_next_question
53 ) {
55 }
56
61 public function toForm(
63 FieldFactory $f,
65 ?array $environment = null
66 ): FormInput {
67 $inputs['title_output'] = $f->radio(
68 $lng->txt('tst_title_output'),
69 $lng->txt('tst_title_output_info')
70 )->withOption('0', $lng->txt('tst_title_output_full'))
71 ->withOption('1', $lng->txt('tst_title_output_hide_points'))
72 ->withOption('3', $lng->txt('tst_title_output_only_points'))
73 ->withOption('2', $lng->txt('tst_title_output_no_title'))
76
77 $inputs['autosave'] = $this->getInputAutosave($lng, $f, $refinery);
78
79 $inputs['shuffle_questions'] = $f->checkbox(
80 $lng->txt('tst_shuffle_questions'),
81 $lng->txt('tst_shuffle_questions_description')
82 )->withValue($this->getShuffleQuestions());
83
84 if ($environment['participant_data_exists']) {
85 $inputs['shuffle_questions'] = $inputs['shuffle_questions']->withDisabled(true);
86 }
87
88 $inputs['instant_feedback'] = $this->getInputInstantFeedback($lng, $f, $refinery, $environment);
89 $inputs['lock_answers'] = $this->getInputLockAnswers($lng, $f, $refinery, $environment);
90
91 $section = $f->section($inputs, $lng->txt('tst_presentation_properties'))
92 ->withAdditionalTransformation($this->getShuffleAndLockAnswersConstraint($lng, $refinery));
93
94 return $section;
95 }
96
97 private function getInputAutosave(
99 FieldFactory $f,
101 ): OptionalGroup {
102 $trafo = $refinery->custom()->transformation(
103 static function (?array $vs): array {
104 if ($vs === null) {
105 return [
106 'autosave_enabled' => false,
107 'autosave_interval' => self::DEFAULT_AUTOSAVE_INTERVAL
108 ];
109 }
110
111 return [
112 'autosave_enabled' => true,
113 'autosave_interval' => $vs['autosave_interval'] * 1000
114 ];
115 }
116 );
117 $sub_inputs_autosave['autosave_interval'] = $f->numeric($lng->txt('autosave_ival'), $lng->txt('seconds'))
118 ->withRequired(true)
119 ->withAdditionalTransformation($refinery->int()->isGreaterThan(0))
120 ->withValue(
121 $this->getAutosaveInterval() !== 0
122 ? $this->getAutosaveInterval() / 1000
123 : 30
124 );
125
126 $autosave_input = $f->optionalGroup(
127 $sub_inputs_autosave,
128 $lng->txt('autosave'),
129 $lng->txt('autosave_info')
130 )->withValue(null)
131 ->withAdditionalTransformation($trafo);
132
133 if (!$this->getAutosaveEnabled()) {
134 return $autosave_input;
135 }
136
137 return $autosave_input->withValue(['autosave_interval' => $this->getAutosaveInterval() / 1000]);
138 }
139
140 private function getInputInstantFeedback(
142 FieldFactory $f,
144 array $environment
145 ): OptionalGroup {
146 $constraint = $refinery->custom()->constraint(
147 fn(?array $vs) => $vs === null
148 || $vs['enabled_feedback_types']['instant_feedback_specific'] === null
149 && $vs['enabled_feedback_types']['instant_feedback_generic'] === null
150 && $vs['enabled_feedback_types']['instant_feedback_points'] === null
151 && $vs['enabled_feedback_types']['instant_feedback_solution'] === null
152 && $vs['feedback_trigger'] === null
153 || (
154 $vs['enabled_feedback_types']['instant_feedback_specific'] === true
155 || $vs['enabled_feedback_types']['instant_feedback_generic'] === true
156 || $vs['enabled_feedback_types']['instant_feedback_points'] === true
157 || $vs['enabled_feedback_types']['instant_feedback_solution'] === true
158 )
159 && $vs['feedback_trigger'] !== '',
160 $lng->txt('select_at_least_one_feedback_type_and_trigger')
161 );
162 $trafo = $refinery->custom()->transformation(
163 static function (?array $vs): array {
164 if ($vs === null) {
165 return [
166 'enabled_feedback_types' => [
167 'instant_feedback_specific' => false,
168 'instant_feedback_generic' => false,
169 'instant_feedback_points' => false,
170 'instant_feedback_solution' => false
171 ],
172 'feedback_on_next_question' => false
173 ];
174 }
175
176 $vs['feedback_on_next_question'] = $vs['feedback_trigger'] === '1';
177 return $vs;
178 }
179 );
180
181 $instant_feedback = $f->optionalGroup(
182 $this->getSubInputInstantFeedback($lng, $f, $environment),
183 $lng->txt('tst_instant_feedback'),
184 $lng->txt('tst_instant_feedback_desc')
185 )->withValue(null)
186 ->withAdditionalTransformation($constraint)
187 ->withAdditionalTransformation($trafo);
188
189 if ($this->isAnyInstantFeedbackOptionEnabled()) {
190 $instant_feedback = $instant_feedback->withValue(
191 [
192 'enabled_feedback_types' => [
193 'instant_feedback_specific' => (bool) $this->getInstantFeedbackSpecificEnabled(),
194 'instant_feedback_generic' => (bool) $this->getInstantFeedbackGenericEnabled(),
195 'instant_feedback_points' => (bool) $this->getInstantFeedbackPointsEnabled(),
196 'instant_feedback_solution' => (bool) $this->getInstantFeedbackSolutionEnabled()
197 ],
198 'feedback_trigger' => ($this->getForceInstantFeedbackOnNextQuestion() ? '1' : '0')
199 ]
200 );
201 }
202
203 if (!$environment['participant_data_exists']) {
204 return $instant_feedback;
205 }
206
207 return $instant_feedback->withDisabled(true);
208 }
209
212 FieldFactory $f,
213 array $environment
214 ): array {
215 $feedback_options = [
216 'instant_feedback_points' => $f->checkbox(
217 $lng->txt('tst_instant_feedback_results'),
218 $lng->txt('tst_instant_feedback_results_desc')
219 ),
220 'instant_feedback_generic' => $f->checkbox(
221 $lng->txt('tst_instant_feedback_answer_generic'),
222 $lng->txt('tst_instant_feedback_answer_generic_desc')
223 ),
224 'instant_feedback_specific' => $f->checkbox(
225 $lng->txt('tst_instant_feedback_answer_specific'),
226 $lng->txt('tst_instant_feedback_answer_specific_desc')
227 ),
228 'instant_feedback_solution' => $f->checkbox(
229 $lng->txt('tst_instant_feedback_solution'),
230 $lng->txt('tst_instant_feedback_solution_desc')
231 )
232 ];
233
234 $sub_inputs_feedback['enabled_feedback_types'] = $f->group(
235 $feedback_options,
236 $lng->txt('tst_instant_feedback_contents')
237 );
238
239 $sub_inputs_feedback['feedback_trigger'] = $f->radio(
240 $lng->txt('tst_instant_feedback_trigger')
241 )->withOption(
242 '0',
243 $lng->txt('tst_instant_feedback_trigger_manual'),
244 $lng->txt('tst_instant_feedback_trigger_manual_desc')
245 )->withOption(
246 '1',
247 $lng->txt('tst_instant_feedback_trigger_forced'),
248 $lng->txt('tst_instant_feedback_trigger_forced_desc')
249 );
250
251 if (!$environment['participant_data_exists']) {
252 $sub_inputs_feedback['feedback_trigger'] = $sub_inputs_feedback['feedback_trigger']
253 ->withRequired(true);
254 }
255 return $sub_inputs_feedback;
256
257
258 }
259
260 private function getInputLockAnswers(
262 FieldFactory $f,
264 array $environment
265 ): SwitchableGroup {
266 $constraint = $refinery->custom()->constraint(
267 static fn(?array $vs): bool => $vs === null || array_filter($vs[1]) !== [],
268 $lng->txt('select_at_least_one_lock_answer_type')
269 );
270
271 $group1 = $f->group([], $lng->txt('tst_answer_fixation_none'));
272
273 $group2 = $f->group(
274 [
275 'lock_answer_on_instant_feedback' => $f->checkbox(
276 $lng->txt('tst_answer_fixation_on_instant_feedback'),
277 $lng->txt('tst_answer_fixation_on_instant_feedback_desc')
278 ),
279 'lock_answer_on_next_question' => $f->checkbox(
280 $lng->txt('tst_answer_fixation_on_followup_question'),
281 $lng->txt('tst_answer_fixation_on_followup_question_desc')
282 )
283 ],
284 $lng->txt('tst_answer_fixation'),
285 $lng->txt('tst_answer_fixation_on_instantfb_or_followupqst_desc')
286 )->withRequired(true);
287
288 return $f->switchableGroup(
289 [self::ANSWER_FIXATION_NONE => $group1, self::ANSWER_FIXATION_ON_IFB_OR_FUQST => $group2],
290 $lng->txt('tst_answer_fixation_handling')
291 )
292 ->withAdditionalTransformation($constraint)
293 ->withAdditionalTransformation($this->getTransformationLockAnswers($refinery))
294 ->withValue($this->getAnswerFixationSettingsAsFormValue())
295 ->withDisabled($environment['participant_data_exists']);
296 }
297
299 {
300 return $refinery->custom()->transformation(
301 static fn(?array $v): array => ($v[0] ?? null) === self::ANSWER_FIXATION_ON_IFB_OR_FUQST
302 ? $v[1]
303 : ['lock_answer_on_instant_feedback' => false, 'lock_answer_on_next_question' => false]
304 );
305 }
306
310 ): Constraint {
311 return $refinery->custom()->constraint(
312 function ($vs): bool {
313 if ($vs['shuffle_questions'] === true
314 && $vs['lock_answers']['lock_answer_on_next_question']) {
315 return false;
316 }
317 return true;
318 },
319 $lng->txt('tst_settings_conflict_shuffle_and_lock'),
320 );
321 }
322
323 public function toStorage(): array
324 {
325 return [
326 'title_output' => ['integer', $this->getQuestionTitleOutputMode()],
327 'autosave' => ['integer', (int) $this->getAutosaveEnabled()],
328 'autosave_ival' => ['integer', $this->getAutosaveInterval()],
329 'shuffle_questions' => ['integer', (int) $this->getShuffleQuestions()],
330 'answer_feedback_points' => ['integer', (int) $this->getInstantFeedbackPointsEnabled()],
331 'answer_feedback' => ['integer', (int) $this->getInstantFeedbackGenericEnabled()],
332 'specific_feedback' => ['integer', (int) $this->getInstantFeedbackSpecificEnabled()],
333 'instant_verification' => ['integer', (int) $this->getInstantFeedbackSolutionEnabled()],
334 'force_inst_fb' => ['integer', (int) $this->getForceInstantFeedbackOnNextQuestion()],
335 'inst_fb_answer_fixation' => ['integer', (int) $this->getLockAnswerOnInstantFeedbackEnabled()],
336 'follow_qst_answer_fixation' => ['integer', (int) $this->getLockAnswerOnNextQuestionEnabled()]
337 ];
338 }
339
340 public function toLog(AdditionalInformationGenerator $additional_info): array
341 {
342
343 switch ($this->getQuestionTitleOutputMode()) {
344 case 0:
345 $log_array[AdditionalInformationGenerator::KEY_TEST_TITLE_PRESENTATION] = $additional_info
346 ->getTagForLangVar('tst_title_output_full');
347 break;
348 case 1:
349 $log_array[AdditionalInformationGenerator::KEY_TEST_TITLE_PRESENTATION] = $additional_info
350 ->getTagForLangVar('tst_title_output_hide_points');
351 break;
352 case 2:
353 $log_array[AdditionalInformationGenerator::KEY_TEST_TITLE_PRESENTATION] = $additional_info
354 ->getTagForLangVar('tst_title_output_no_title');
355 break;
356 case 3:
357 $log_array[AdditionalInformationGenerator::KEY_TEST_TITLE_PRESENTATION] = $additional_info
358 ->getTagForLangVar('tst_title_output_only_points');
359 break;
360 }
361 $log_array[AdditionalInformationGenerator::KEY_TEST_AUTOSAVE_ENABLED] = $this->getAutosaveEnabled()
362 ? $this->getAutosaveInterval() / 1000 . ' ' . $additional_info->getTagForLangVar('seconds') : $additional_info->getEnabledDisabledTagForBool(false);
363 $log_array[AdditionalInformationGenerator::KEY_TEST_SHUFFLE_QUESTIONS] = $additional_info
364 ->getEnabledDisabledTagForBool($this->getShuffleQuestions());
365
366 $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_ENABLED] = $additional_info
367 ->getEnabledDisabledTagForBool($this->isAnyInstantFeedbackOptionEnabled());
368 if ($this->isAnyInstantFeedbackOptionEnabled()) {
369 $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_SHOW_POINTS] = $additional_info
370 ->getEnabledDisabledTagForBool($this->getInstantFeedbackPointsEnabled());
371 $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_SHOW_GENERIC] = $additional_info
372 ->getEnabledDisabledTagForBool($this->getInstantFeedbackGenericEnabled());
373 $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_SHOW_SPECIFIC] = $additional_info
374 ->getEnabledDisabledTagForBool($this->getInstantFeedbackSpecificEnabled());
375 $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_SHOW_SOLUTION] = $additional_info
376 ->getEnabledDisabledTagForBool($this->getInstantFeedbackSolutionEnabled());
377 $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_TRIGGER] = $this->getForceInstantFeedbackOnNextQuestion()
378 ? $additional_info->getTagForLangVar('tst_instant_feedback_trigger_forced')
379 : $additional_info->getTagForLangVar('tst_instant_feedback_trigger_manual');
380 }
381
382 $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_none');
383 if ($this->getLockAnswerOnInstantFeedbackEnabled()
384 && $this->getLockAnswerOnNextQuestionEnabled()) {
385 $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_instantfb_or_followupqst');
386 } elseif ($this->getLockAnswerOnInstantFeedbackEnabled()) {
387 $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_instant_feedback');
388 } elseif ($this->getLockAnswerOnNextQuestionEnabled()) {
389 $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_followup_question');
390 }
391 $log_array[AdditionalInformationGenerator::KEY_TEST_LOCK_ANSWERS_MODE] = $lock_answers;
392
393 return $log_array;
394 }
395
397 {
398 return $this->question_title_output_mode;
399 }
400
401 public function withQuestionTitleOutputMode(int $question_title_output_mode): self
402 {
403 $clone = clone $this;
404 $clone->question_title_output_mode = $question_title_output_mode;
405 return $clone;
406 }
407
408 public function getAutosaveEnabled(): bool
409 {
410 return $this->autosave_enabled;
411 }
412
413 public function withAutosaveEnabled(bool $autosave_enabled): self
414 {
415 $clone = clone $this;
416 $clone->autosave_enabled = $autosave_enabled;
417 return $clone;
418 }
419
420 public function getAutosaveInterval(): int
421 {
422 return $this->autosave_interval;
423 }
424
425 public function withAutosaveInterval(int $autosave_interval): self
426 {
427 $clone = clone $this;
428 $clone->autosave_interval = $autosave_interval;
429 return $clone;
430 }
431
432 public function getShuffleQuestions(): bool
433 {
434 return $this->shuffle_questions;
435 }
436
437 public function withShuffleQuestions(bool $shuffle_questions): self
438 {
439 $clone = clone $this;
440 $clone->shuffle_questions = $shuffle_questions;
441 return $clone;
442 }
443
444 public function getInstantFeedbackPointsEnabled(): bool
445 {
446 return $this->instant_feedback_points_enabled;
447 }
448
449 public function withInstantFeedbackPointsEnabled(bool $instant_feedback_points_enabled): self
450 {
451 $clone = clone $this;
452 $clone->instant_feedback_points_enabled = $instant_feedback_points_enabled;
453 return $clone;
454 }
455
456 public function getInstantFeedbackGenericEnabled(): bool
457 {
458 return $this->instant_feedback_generic_enabled;
459 }
460
461 public function withInstantFeedbackGenericEnabled(bool $instant_feedback_generic_enabled): self
462 {
463 $clone = clone $this;
464 $clone->instant_feedback_generic_enabled = $instant_feedback_generic_enabled;
465 return $clone;
466 }
467
468 public function getInstantFeedbackSpecificEnabled(): bool
469 {
470 return $this->instant_feedback_specific_enabled;
471 }
472
473 public function withInstantFeedbackSpecificEnabled(bool $instant_feedback_specific_enabled): self
474 {
475 $clone = clone $this;
476 $clone->instant_feedback_specific_enabled = $instant_feedback_specific_enabled;
477 return $clone;
478 }
479
480 public function getInstantFeedbackSolutionEnabled(): bool
481 {
482 return $this->instant_feedback_solution_enabled;
483 }
484
485 public function withInstantFeedbackSolutionEnabled(bool $instant_feedback_solution_enabled): self
486 {
487 $clone = clone $this;
488 $clone->instant_feedback_solution_enabled = $instant_feedback_solution_enabled;
489 return $clone;
490 }
491
492 private function isAnyInstantFeedbackOptionEnabled(): bool
493 {
494 return $this->getInstantFeedbackPointsEnabled()
495 || $this->getInstantFeedbackGenericEnabled()
496 || $this->getInstantFeedbackSpecificEnabled()
497 || $this->getInstantFeedbackSolutionEnabled();
498 }
499
501 {
502 return $this->force_instant_feedback_on_next_question;
503 }
504
505 public function withForceInstantFeedbackOnNextQuestion(bool $force_instant_feedback_on_next_question): self
506 {
507 $clone = clone $this;
508 $clone->force_instant_feedback_on_next_question = $force_instant_feedback_on_next_question;
509 return $clone;
510 }
511
513 {
514 return $this->lock_answer_on_instant_feedback;
515 }
516
517 public function withLockAnswerOnInstantFeedbackEnabled(bool $lock_answer_on_instant_feedback): self
518 {
519 $clone = clone $this;
520 $clone->lock_answer_on_instant_feedback = $lock_answer_on_instant_feedback;
521 return $clone;
522 }
523
525 {
526 return $this->lock_answer_on_next_question;
527 }
528
529 public function withLockAnswerOnNextQuestionEnabled(bool $lock_answer_on_next_question): self
530 {
531 $clone = clone $this;
532 $clone->lock_answer_on_next_question = $lock_answer_on_next_question;
533 return $clone;
534 }
535
536 private function getAnswerFixationSettingsAsFormValue(): array
537 {
538 if ($this->getLockAnswerOnInstantFeedbackEnabled() || $this->getLockAnswerOnNextQuestionEnabled()) {
539 return [
540 0 => self::ANSWER_FIXATION_ON_IFB_OR_FUQST,
541 1 => [
542 'lock_answer_on_instant_feedback' => $this->getLockAnswerOnInstantFeedbackEnabled(),
543 'lock_answer_on_next_question' => $this->getLockAnswerOnNextQuestionEnabled()
544 ]
545 ];
546 }
547
548 return [0 => self::ANSWER_FIXATION_NONE, 1 => []];
549 }
550}
Builds data types.
Definition: Factory.php:36
withInstantFeedbackGenericEnabled(bool $instant_feedback_generic_enabled)
__construct(int $test_id, protected int $question_title_output_mode, protected bool $autosave_enabled, protected int $autosave_interval, protected bool $shuffle_questions, protected bool $instant_feedback_points_enabled, protected bool $instant_feedback_generic_enabled, protected bool $instant_feedback_specific_enabled, protected bool $instant_feedback_solution_enabled, protected bool $force_instant_feedback_on_next_question, protected bool $lock_answer_on_instant_feedback, protected bool $lock_answer_on_next_question)
withLockAnswerOnInstantFeedbackEnabled(bool $lock_answer_on_instant_feedback)
getInputAutosave(\ilLanguage $lng, FieldFactory $f, Refinery $refinery)
withForceInstantFeedbackOnNextQuestion(bool $force_instant_feedback_on_next_question)
withInstantFeedbackSpecificEnabled(bool $instant_feedback_specific_enabled)
getInputInstantFeedback(\ilLanguage $lng, FieldFactory $f, Refinery $refinery, array $environment)
toForm(\ilLanguage $lng, FieldFactory $f, Refinery $refinery, ?array $environment=null)
withInstantFeedbackPointsEnabled(bool $instant_feedback_points_enabled)
getShuffleAndLockAnswersConstraint(\ilLanguage $lng, Refinery $refinery)
toLog(AdditionalInformationGenerator $additional_info)
getSubInputInstantFeedback(\ilLanguage $lng, FieldFactory $f, array $environment)
getInputLockAnswers(\ilLanguage $lng, FieldFactory $f, Refinery $refinery, array $environment)
withInstantFeedbackSolutionEnabled(bool $instant_feedback_solution_enabled)
return true
language handling
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
A constraint encodes some resrtictions on values.
Definition: Constraint.php:32
A transformation is a function from one datatype to another.
This describes inputs that can be used in forms.
Definition: FormInput.php:33
This is what a factory for input fields looks like.
Definition: Factory.php:31
This describes optional group inputs.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
withAdditionalTransformation(Transformation $trafo)
@inheritDoc
withValue($value)
Get an input like this with another value displayed on the client side.
Definition: Group.php:61
global $lng
Definition: privfeed.php:31
if(!file_exists('../ilias.ini.php'))