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