ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
SettingsQuestionBehaviour.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
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 $instant_feedback_points_enabled,
49  protected bool $instant_feedback_generic_enabled,
50  protected bool $instant_feedback_specific_enabled,
51  protected bool $instant_feedback_solution_enabled,
52  protected bool $force_instant_feedback_on_next_question,
53  protected bool $lock_answer_on_instant_feedback,
54  protected bool $lock_answer_on_next_question
55  ) {
56  parent::__construct($test_id);
57  }
58 
63  public function toForm(
67  ?array $environment = null
68  ): FormInput {
69  $inputs['title_output'] = $f->radio(
70  $lng->txt('tst_title_output'),
71  $lng->txt('tst_title_output_info')
72  )->withOption('0', $lng->txt('tst_title_output_full'))
73  ->withOption('1', $lng->txt('tst_title_output_hide_points'))
74  ->withOption('3', $lng->txt('tst_title_output_only_points'))
75  ->withOption('2', $lng->txt('tst_title_output_no_title'))
77  ->withAdditionalTransformation($refinery->kindlyTo()->int());
78 
79  $inputs['autosave'] = $this->getInputAutosave($lng, $f, $refinery);
80 
81  $inputs['shuffle_questions'] = $f->checkbox(
82  $lng->txt('tst_shuffle_questions'),
83  $lng->txt('tst_shuffle_questions_description')
84  )->withValue($this->getShuffleQuestions());
85 
86  if ($environment['participant_data_exists']) {
87  $inputs['shuffle_questions'] = $inputs['shuffle_questions']->withDisabled(true);
88  }
89 
90  $inputs['instant_feedback'] = $this->getInputInstantFeedback($lng, $f, $refinery, $environment);
91  $inputs['lock_answers'] = $this->getInputLockAnswers($lng, $f, $refinery, $environment);
92 
93  $section = $f->section($inputs, $lng->txt('tst_presentation_properties'))
95 
96  return $section;
97  }
98 
99  private function getInputAutosave(
100  \ilLanguage $lng,
103  ): OptionalGroup {
104  $trafo = $refinery->custom()->transformation(
105  static function (?array $vs): array {
106  if ($vs === null) {
107  return [
108  'autosave_enabled' => false,
109  'autosave_interval' => self::DEFAULT_AUTOSAVE_INTERVAL
110  ];
111  }
112 
113  return [
114  'autosave_enabled' => true,
115  'autosave_interval' => $vs['autosave_interval'] * 1000
116  ];
117  }
118  );
119  $sub_inputs_autosave['autosave_interval'] = $f->numeric($lng->txt('autosave_ival'), $lng->txt('seconds'))
120  ->withRequired(true)
121  ->withAdditionalTransformation($refinery->int()->isGreaterThan(0))
122  ->withValue(
123  $this->getAutosaveInterval() !== 0
124  ? $this->getAutosaveInterval() / 1000
125  : 30
126  );
127 
128  $autosave_input = $f->optionalGroup(
129  $sub_inputs_autosave,
130  $lng->txt('autosave'),
131  $lng->txt('autosave_info')
132  )->withValue(null)
133  ->withAdditionalTransformation($trafo);
134 
135  if (!$this->getAutosaveEnabled()) {
136  return $autosave_input;
137  }
138 
139  return $autosave_input->withValue(['autosave_interval' => $this->getAutosaveInterval() / 1000]);
140  }
141 
142  private function getInputInstantFeedback(
143  \ilLanguage $lng,
146  array $environment
147  ): OptionalGroup {
148  $constraint = $refinery->custom()->constraint(
149  fn(?array $vs) => $vs === null
150  || $vs['enabled_feedback_types']['instant_feedback_specific'] === null
151  && $vs['enabled_feedback_types']['instant_feedback_generic'] === null
152  && $vs['enabled_feedback_types']['instant_feedback_points'] === null
153  && $vs['enabled_feedback_types']['instant_feedback_solution'] === null
154  && $vs['feedback_trigger'] === null
155  || (
156  $vs['enabled_feedback_types']['instant_feedback_specific'] === true
157  || $vs['enabled_feedback_types']['instant_feedback_generic'] === true
158  || $vs['enabled_feedback_types']['instant_feedback_points'] === true
159  || $vs['enabled_feedback_types']['instant_feedback_solution'] === true
160  )
161  && $vs['feedback_trigger'] !== '',
162  $lng->txt('select_at_least_one_feedback_type_and_trigger')
163  );
164  $trafo = $refinery->custom()->transformation(
165  static function (?array $vs): array {
166  if ($vs === null) {
167  return [
168  'enabled_feedback_types' => [
169  'instant_feedback_specific' => false,
170  'instant_feedback_generic' => false,
171  'instant_feedback_points' => false,
172  'instant_feedback_solution' => false
173  ],
174  'feedback_on_next_question' => false
175  ];
176  }
177 
178  $vs['feedback_on_next_question'] = $vs['feedback_trigger'] === '1';
179  return $vs;
180  }
181  );
182 
183  $instant_feedback = $f->optionalGroup(
184  $this->getSubInputInstantFeedback($lng, $f, $environment),
185  $lng->txt('tst_instant_feedback'),
186  $lng->txt('tst_instant_feedback_desc')
187  )->withValue(null)
188  ->withAdditionalTransformation($constraint)
189  ->withAdditionalTransformation($trafo);
190 
191  if ($this->isAnyInstantFeedbackOptionEnabled()) {
192  $instant_feedback = $instant_feedback->withValue(
193  [
194  'enabled_feedback_types' => [
195  'instant_feedback_specific' => (bool) $this->getInstantFeedbackSpecificEnabled(),
196  'instant_feedback_generic' => (bool) $this->getInstantFeedbackGenericEnabled(),
197  'instant_feedback_points' => (bool) $this->getInstantFeedbackPointsEnabled(),
198  'instant_feedback_solution' => (bool) $this->getInstantFeedbackSolutionEnabled()
199  ],
200  'feedback_trigger' => ($this->getForceInstantFeedbackOnNextQuestion() ? '1' : '0')
201  ]
202  );
203  }
204 
205  if (!$environment['participant_data_exists']) {
206  return $instant_feedback;
207  }
208 
209  return $instant_feedback->withDisabled(true);
210  }
211 
212  private function getSubInputInstantFeedback(
213  \ilLanguage $lng,
215  array $environment
216  ): array {
217  $feedback_options = [
218  'instant_feedback_points' => $f->checkbox(
219  $lng->txt('tst_instant_feedback_results'),
220  $lng->txt('tst_instant_feedback_results_desc')
221  ),
222  'instant_feedback_generic' => $f->checkbox(
223  $lng->txt('tst_instant_feedback_answer_generic'),
224  $lng->txt('tst_instant_feedback_answer_generic_desc')
225  ),
226  'instant_feedback_specific' => $f->checkbox(
227  $lng->txt('tst_instant_feedback_answer_specific'),
228  $lng->txt('tst_instant_feedback_answer_specific_desc')
229  ),
230  'instant_feedback_solution' => $f->checkbox(
231  $lng->txt('tst_instant_feedback_solution'),
232  $lng->txt('tst_instant_feedback_solution_desc')
233  )
234  ];
235 
236  $sub_inputs_feedback['enabled_feedback_types'] = $f->group(
237  $feedback_options,
238  $lng->txt('tst_instant_feedback_contents')
239  );
240 
241  $sub_inputs_feedback['feedback_trigger'] = $f->radio(
242  $lng->txt('tst_instant_feedback_trigger')
243  )->withOption(
244  '0',
245  $lng->txt('tst_instant_feedback_trigger_manual'),
246  $lng->txt('tst_instant_feedback_trigger_manual_desc')
247  )->withOption(
248  '1',
249  $lng->txt('tst_instant_feedback_trigger_forced'),
250  $lng->txt('tst_instant_feedback_trigger_forced_desc')
251  );
252 
253  if (!$environment['participant_data_exists']) {
254  $sub_inputs_feedback['feedback_trigger'] = $sub_inputs_feedback['feedback_trigger']
255  ->withRequired(true);
256  }
257  return $sub_inputs_feedback;
258 
259 
260  }
261 
262  private function getInputLockAnswers(
263  \ilLanguage $lng,
266  array $environment
267  ): Radio {
268  $lock_answers = $f->radio(
269  $lng->txt('tst_answer_fixation_handling')
270  )->withOption(
271  self::ANSWER_FIXATION_NONE,
272  $lng->txt('tst_answer_fixation_none'),
273  $lng->txt('tst_answer_fixation_none_desc')
274  )->withOption(
275  self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK,
276  $lng->txt('tst_answer_fixation_on_instant_feedback'),
277  $lng->txt('tst_answer_fixation_on_instant_feedback_desc')
278  )->withOption(
279  self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION,
280  $lng->txt('tst_answer_fixation_on_followup_question'),
281  $lng->txt('tst_answer_fixation_on_followup_question_desc')
282  )->withOption(
283  self::ANSWER_FIXATION_ON_IFB_OR_FUQST,
284  $lng->txt('tst_answer_fixation_on_instantfb_or_followupqst'),
285  $lng->txt('tst_answer_fixation_on_instantfb_or_followupqst_desc')
286  )->withValue(
289 
290  if (!$environment['participant_data_exists']) {
291  return $lock_answers;
292  }
293 
294  return $lock_answers->withDisabled(true);
295  }
296 
298  {
299  return $refinery->custom()->transformation(
300  static function (?string $v): array {
301  if ($v === null || $v === self::ANSWER_FIXATION_NONE) {
302  return [
303  'lock_answer_on_instant_feedback' => false,
304  'lock_answer_on_next_question' => false
305  ];
306  }
307 
308  if ($v === self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK) {
309  return [
310  'lock_answer_on_instant_feedback' => true,
311  'lock_answer_on_next_question' => false
312  ];
313  }
314  if ($v === self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION) {
315  return [
316  'lock_answer_on_instant_feedback' => false,
317  'lock_answer_on_next_question' => true
318  ];
319  }
320 
321  return [
322  'lock_answer_on_instant_feedback' => true,
323  'lock_answer_on_next_question' => true
324  ];
325  }
326  );
327  }
328 
330  \ilLanguage $lng,
332  ): Constraint {
333  return $refinery->custom()->constraint(
334  function ($vs): bool {
335  if ($vs['shuffle_questions'] === true
336  && $vs['lock_answers']['lock_answer_on_next_question']) {
337  return false;
338  }
339  return true;
340  },
341  $lng->txt('tst_settings_conflict_shuffle_and_lock'),
342  );
343  }
344 
345  public function toStorage(): array
346  {
347  return [
348  'title_output' => ['integer', $this->getQuestionTitleOutputMode()],
349  'autosave' => ['integer', (int) $this->getAutosaveEnabled()],
350  'autosave_ival' => ['integer', $this->getAutosaveInterval()],
351  'shuffle_questions' => ['integer', (int) $this->getShuffleQuestions()],
352  'answer_feedback_points' => ['integer', (int) $this->getInstantFeedbackPointsEnabled()],
353  'answer_feedback' => ['integer', (int) $this->getInstantFeedbackGenericEnabled()],
354  'specific_feedback' => ['integer', (int) $this->getInstantFeedbackSpecificEnabled()],
355  'instant_verification' => ['integer', (int) $this->getInstantFeedbackSolutionEnabled()],
356  'force_inst_fb' => ['integer', (int) $this->getForceInstantFeedbackOnNextQuestion()],
357  'inst_fb_answer_fixation' => ['integer', (int) $this->getLockAnswerOnInstantFeedbackEnabled()],
358  'follow_qst_answer_fixation' => ['integer', (int) $this->getLockAnswerOnNextQuestionEnabled()]
359  ];
360  }
361 
362  public function toLog(AdditionalInformationGenerator $additional_info): array
363  {
364 
365  switch ($this->getQuestionTitleOutputMode()) {
366  case 0:
368  ->getTagForLangVar('tst_title_output_full');
369  break;
370  case 1:
372  ->getTagForLangVar('tst_title_output_hide_points');
373  break;
374  case 2:
376  ->getTagForLangVar('tst_title_output_no_title');
377  break;
378  case 3:
380  ->getTagForLangVar('tst_title_output_only_points');
381  break;
382  }
384  ? $this->getAutosaveInterval() / 1000 . ' ' . $additional_info->getTagForLangVar('seconds') : $additional_info->getEnabledDisabledTagForBool(false);
387 
388  $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_ENABLED] = $additional_info
390  if ($this->isAnyInstantFeedbackOptionEnabled()) {
400  ? $additional_info->getTagForLangVar('tst_instant_feedback_trigger_forced')
401  : $additional_info->getTagForLangVar('tst_instant_feedback_trigger_manual');
402  }
403 
404  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_none');
407  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_instantfb_or_followupqst');
408  } elseif ($this->getLockAnswerOnInstantFeedbackEnabled()) {
409  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_instant_feedback');
410  } elseif ($this->getLockAnswerOnNextQuestionEnabled()) {
411  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_followup_question');
412  }
414 
415  return $log_array;
416  }
417 
418  public function getQuestionTitleOutputMode(): int
419  {
420  return $this->question_title_output_mode;
421  }
422 
423  public function withQuestionTitleOutputMode(int $question_title_output_mode): self
424  {
425  $clone = clone $this;
426  $clone->question_title_output_mode = $question_title_output_mode;
427  return $clone;
428  }
429 
430  public function getAutosaveEnabled(): bool
431  {
432  return $this->autosave_enabled;
433  }
434 
435  public function withAutosaveEnabled(bool $autosave_enabled): self
436  {
437  $clone = clone $this;
438  $clone->autosave_enabled = $autosave_enabled;
439  return $clone;
440  }
441 
442  public function getAutosaveInterval(): int
443  {
444  return $this->autosave_interval;
445  }
446 
447  public function withAutosaveInterval(int $autosave_interval): self
448  {
449  $clone = clone $this;
450  $clone->autosave_interval = $autosave_interval;
451  return $clone;
452  }
453 
454  public function getShuffleQuestions(): bool
455  {
456  return $this->shuffle_questions;
457  }
458 
459  public function withShuffleQuestions(bool $shuffle_questions): self
460  {
461  $clone = clone $this;
462  $clone->shuffle_questions = $shuffle_questions;
463  return $clone;
464  }
465 
466  public function getInstantFeedbackPointsEnabled(): bool
467  {
468  return $this->instant_feedback_points_enabled;
469  }
470 
471  public function withInstantFeedbackPointsEnabled(bool $instant_feedback_points_enabled): self
472  {
473  $clone = clone $this;
474  $clone->instant_feedback_points_enabled = $instant_feedback_points_enabled;
475  return $clone;
476  }
477 
478  public function getInstantFeedbackGenericEnabled(): bool
479  {
480  return $this->instant_feedback_generic_enabled;
481  }
482 
483  public function withInstantFeedbackGenericEnabled(bool $instant_feedback_generic_enabled): self
484  {
485  $clone = clone $this;
486  $clone->instant_feedback_generic_enabled = $instant_feedback_generic_enabled;
487  return $clone;
488  }
489 
490  public function getInstantFeedbackSpecificEnabled(): bool
491  {
492  return $this->instant_feedback_specific_enabled;
493  }
494 
495  public function withInstantFeedbackSpecificEnabled(bool $instant_feedback_specific_enabled): self
496  {
497  $clone = clone $this;
498  $clone->instant_feedback_specific_enabled = $instant_feedback_specific_enabled;
499  return $clone;
500  }
501 
502  public function getInstantFeedbackSolutionEnabled(): bool
503  {
504  return $this->instant_feedback_solution_enabled;
505  }
506 
507  public function withInstantFeedbackSolutionEnabled(bool $instant_feedback_solution_enabled): self
508  {
509  $clone = clone $this;
510  $clone->instant_feedback_solution_enabled = $instant_feedback_solution_enabled;
511  return $clone;
512  }
513 
514  private function isAnyInstantFeedbackOptionEnabled(): bool
515  {
516  return $this->getInstantFeedbackPointsEnabled()
520  }
521 
523  {
524  return $this->force_instant_feedback_on_next_question;
525  }
526 
527  public function withForceInstantFeedbackOnNextQuestion(bool $force_instant_feedback_on_next_question): self
528  {
529  $clone = clone $this;
530  $clone->force_instant_feedback_on_next_question = $force_instant_feedback_on_next_question;
531  return $clone;
532  }
533 
535  {
536  return $this->lock_answer_on_instant_feedback;
537  }
538 
539  public function withLockAnswerOnInstantFeedbackEnabled(bool $lock_answer_on_instant_feedback): self
540  {
541  $clone = clone $this;
542  $clone->lock_answer_on_instant_feedback = $lock_answer_on_instant_feedback;
543  return $clone;
544  }
545 
546  public function getLockAnswerOnNextQuestionEnabled(): bool
547  {
548  return $this->lock_answer_on_next_question;
549  }
550 
551  public function withLockAnswerOnNextQuestionEnabled(bool $lock_answer_on_next_question): self
552  {
553  $clone = clone $this;
554  $clone->lock_answer_on_next_question = $lock_answer_on_next_question;
555  return $clone;
556  }
557 
558  private function getAnswerFixationSettingsAsFormValue(): string
559  {
562  return self::ANSWER_FIXATION_ON_IFB_OR_FUQST;
563  }
564 
566  return self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK;
567  }
568 
569  if ($this->getLockAnswerOnNextQuestionEnabled()) {
570  return self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION;
571  }
572 
573  return self::ANSWER_FIXATION_NONE;
574  }
575 }
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.
toForm(\ilLanguage $lng, FieldFactory $f, Refinery $refinery, ?array $environment=null)
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)
__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)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
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:61
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:31
getShuffleAndLockAnswersConstraint(\ilLanguage $lng, Refinery $refinery)
A transformation is a function from one datatype to another.
This describes inputs that can be used in forms.
Definition: FormInput.php:32
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)