ILIAS  trunk Revision v11.0_alpha-1723-g8e69f309bab
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
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 $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(
68  ?array $environment = null
69  ): FormInput {
70  $inputs['title_output'] = $f->radio($lng->txt('tst_title_output'))
71  ->withOption('0', $lng->txt('tst_title_output_full'))
72  ->withOption('1', $lng->txt('tst_title_output_hide_points'))
73  ->withOption('3', $lng->txt('tst_title_output_only_points'))
74  ->withOption('2', $lng->txt('tst_title_output_no_title'))
76  ->withAdditionalTransformation($refinery->kindlyTo()->int())
77  ;
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  $inputs['offer_hints'] = $f->checkbox(
87  $lng->txt('tst_setting_offer_hints_label'),
88  $lng->txt('tst_setting_offer_hints_info')
90 
91  if ($environment['participant_data_exists']) {
92  $inputs['shuffle_questions'] = $inputs['shuffle_questions']->withDisabled(true);
93  $inputs['offer_hints'] = $inputs['offer_hints']->withDisabled(true);
94  }
95 
96  $inputs['instant_feedback'] = $this->getInputInstantFeedback($lng, $f, $refinery, $environment);
97  $inputs['lock_answers'] = $this->getInputLockAnswers($lng, $f, $refinery, $environment);
98 
99  $section = $f->section($inputs, $lng->txt('tst_presentation_properties'))
101 
102  return $section;
103  }
104 
105  private function getInputAutosave(
106  \ilLanguage $lng,
109  ): OptionalGroup {
110  $trafo = $refinery->custom()->transformation(
111  static function (?array $vs): array {
112  if ($vs === null) {
113  return [
114  'autosave_enabled' => false,
115  'autosave_interval' => self::DEFAULT_AUTOSAVE_INTERVAL
116  ];
117  }
118 
119  return [
120  'autosave_enabled' => true,
121  'autosave_interval' => $vs['autosave_interval'] * 1000
122  ];
123  }
124  );
125  $sub_inputs_autosave['autosave_interval'] = $f->numeric($lng->txt('autosave_ival'), $lng->txt('seconds'))
126  ->withRequired(true)
127  ->withAdditionalTransformation($refinery->int()->isGreaterThan(0))
128  ->withValue(
129  $this->getAutosaveInterval() !== 0
130  ? $this->getAutosaveInterval() / 1000
131  : 30
132  );
133 
134  $autosave_input = $f->optionalGroup(
135  $sub_inputs_autosave,
136  $lng->txt('autosave'),
137  $lng->txt('autosave_info')
138  )->withValue(null)
139  ->withAdditionalTransformation($trafo);
140 
141  if (!$this->getAutosaveEnabled()) {
142  return $autosave_input;
143  }
144 
145  return $autosave_input->withValue(['autosave_interval' => $this->getAutosaveInterval() / 1000]);
146  }
147 
148  private function getInputInstantFeedback(
149  \ilLanguage $lng,
152  array $environment
153  ): OptionalGroup {
154  $constraint = $refinery->custom()->constraint(
155  fn(?array $vs) => $vs === null
156  || $vs['enabled_feedback_types']['instant_feedback_specific'] === null
157  && $vs['enabled_feedback_types']['instant_feedback_generic'] === null
158  && $vs['enabled_feedback_types']['instant_feedback_points'] === null
159  && $vs['enabled_feedback_types']['instant_feedback_solution'] === null
160  && $vs['feedback_trigger'] === null
161  || (
162  $vs['enabled_feedback_types']['instant_feedback_specific'] === true
163  || $vs['enabled_feedback_types']['instant_feedback_generic'] === true
164  || $vs['enabled_feedback_types']['instant_feedback_points'] === true
165  || $vs['enabled_feedback_types']['instant_feedback_solution'] === true
166  )
167  && $vs['feedback_trigger'] !== '',
168  $lng->txt('select_at_least_one_feedback_type_and_trigger')
169  );
170  $trafo = $refinery->custom()->transformation(
171  static function (?array $vs): array {
172  if ($vs === null) {
173  return [
174  'enabled_feedback_types' => [
175  'instant_feedback_specific' => false,
176  'instant_feedback_generic' => false,
177  'instant_feedback_points' => false,
178  'instant_feedback_solution' => false
179  ],
180  'feedback_on_next_question' => false
181  ];
182  }
183 
184  $vs['feedback_on_next_question'] = $vs['feedback_trigger'] === '1';
185  return $vs;
186  }
187  );
188 
189  $instant_feedback = $f->optionalGroup(
190  $this->getSubInputInstantFeedback($lng, $f),
191  $lng->txt('tst_instant_feedback'),
192  $lng->txt('tst_instant_feedback_desc')
193  )->withValue(null)
194  ->withAdditionalTransformation($constraint)
195  ->withAdditionalTransformation($trafo);
196 
197  if ($this->isAnyInstantFeedbackOptionEnabled()) {
198  $instant_feedback = $instant_feedback->withValue(
199  [
200  'enabled_feedback_types' => [
201  'instant_feedback_specific' => (bool) $this->getInstantFeedbackSpecificEnabled(),
202  'instant_feedback_generic' => (bool) $this->getInstantFeedbackGenericEnabled(),
203  'instant_feedback_points' => (bool) $this->getInstantFeedbackPointsEnabled(),
204  'instant_feedback_solution' => (bool) $this->getInstantFeedbackSolutionEnabled()
205  ],
206  'feedback_trigger' => ($this->getForceInstantFeedbackOnNextQuestion() ? '1' : '0')
207  ]
208  );
209  }
210 
211  if (!$environment['participant_data_exists']) {
212  return $instant_feedback;
213  }
214 
215  return $instant_feedback->withDisabled(true);
216  }
217 
218  private function getSubInputInstantFeedback(
219  \ilLanguage $lng,
221  ): array {
222  $feedback_options = [
223  'instant_feedback_points' => $f->checkbox(
224  $lng->txt('tst_instant_feedback_results'),
225  $lng->txt('tst_instant_feedback_results_desc')
226  ),
227  'instant_feedback_generic' => $f->checkbox(
228  $lng->txt('tst_instant_feedback_answer_generic'),
229  $lng->txt('tst_instant_feedback_answer_generic_desc')
230  ),
231  'instant_feedback_specific' => $f->checkbox(
232  $lng->txt('tst_instant_feedback_answer_specific'),
233  $lng->txt('tst_instant_feedback_answer_specific_desc')
234  ),
235  'instant_feedback_solution' => $f->checkbox(
236  $lng->txt('tst_instant_feedback_solution'),
237  $lng->txt('tst_instant_feedback_solution_desc')
238  )
239  ];
240 
241  $sub_inputs_feedback['enabled_feedback_types'] = $f->group(
242  $feedback_options,
243  $lng->txt('tst_instant_feedback_contents')
244  );
245 
246  $sub_inputs_feedback['feedback_trigger'] = $f->radio(
247  $lng->txt('tst_instant_feedback_trigger')
248  )->withOption(
249  '0',
250  $lng->txt('tst_instant_feedback_trigger_manual'),
251  $lng->txt('tst_instant_feedback_trigger_manual_desc')
252  )->withOption(
253  '1',
254  $lng->txt('tst_instant_feedback_trigger_forced'),
255  $lng->txt('tst_instant_feedback_trigger_forced_desc')
256  )->withRequired(true);
257 
258  return $sub_inputs_feedback;
259  }
260 
261  private function getInputLockAnswers(
262  \ilLanguage $lng,
265  array $environment
266  ): Radio {
267  $lock_answers = $f->radio(
268  $lng->txt('tst_answer_fixation_handling')
269  )->withOption(
270  self::ANSWER_FIXATION_NONE,
271  $lng->txt('tst_answer_fixation_none'),
272  $lng->txt('tst_answer_fixation_none_desc')
273  )->withOption(
274  self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK,
275  $lng->txt('tst_answer_fixation_on_instant_feedback'),
276  $lng->txt('tst_answer_fixation_on_instant_feedback_desc')
277  )->withOption(
278  self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION,
279  $lng->txt('tst_answer_fixation_on_followup_question'),
280  $lng->txt('tst_answer_fixation_on_followup_question_desc')
281  )->withOption(
282  self::ANSWER_FIXATION_ON_IFB_OR_FUQST,
283  $lng->txt('tst_answer_fixation_on_instantfb_or_followupqst'),
284  $lng->txt('tst_answer_fixation_on_instantfb_or_followupqst_desc')
285  )->withValue(
288 
289  if (!$environment['participant_data_exists']) {
290  return $lock_answers;
291  }
292 
293  return $lock_answers->withDisabled(true);
294  }
295 
297  {
298  return $refinery->custom()->transformation(
299  static function (?string $v): array {
300  if ($v === null || $v === self::ANSWER_FIXATION_NONE) {
301  return [
302  'lock_answer_on_instant_feedback' => false,
303  'lock_answer_on_next_question' => false
304  ];
305  }
306 
307  if ($v === self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK) {
308  return [
309  'lock_answer_on_instant_feedback' => true,
310  'lock_answer_on_next_question' => false
311  ];
312  }
313  if ($v === self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION) {
314  return [
315  'lock_answer_on_instant_feedback' => false,
316  'lock_answer_on_next_question' => true
317  ];
318  }
319 
320  return [
321  'lock_answer_on_instant_feedback' => true,
322  'lock_answer_on_next_question' => true
323  ];
324  }
325  );
326  }
327 
329  \ilLanguage $lng,
331  ): Constraint {
332  return $refinery->custom()->constraint(
333  function ($vs): bool {
334  if ($vs['shuffle_questions'] === true
335  && $vs['lock_answers']['lock_answer_on_next_question']) {
336  return false;
337  }
338  return true;
339  },
340  $lng->txt('tst_settings_conflict_shuffle_and_lock'),
341  );
342  }
343 
344  public function toStorage(): array
345  {
346  return [
347  'title_output' => ['integer', $this->getQuestionTitleOutputMode()],
348  'autosave' => ['integer', (int) $this->getAutosaveEnabled()],
349  'autosave_ival' => ['integer', $this->getAutosaveInterval()],
350  'shuffle_questions' => ['integer', (int) $this->getShuffleQuestions()],
351  'offer_question_hints' => ['integer', (int) $this->getQuestionHintsEnabled()],
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  $log_array[AdditionalInformationGenerator::KEY_TEST_HINTS_ENABLED] = $additional_info
389 
390  $log_array[AdditionalInformationGenerator::KEY_TEST_FEEDBACK_ENABLED] = $additional_info
392  if ($this->isAnyInstantFeedbackOptionEnabled()) {
402  ? $additional_info->getTagForLangVar('tst_instant_feedback_trigger_forced')
403  : $additional_info->getTagForLangVar('tst_instant_feedback_trigger_manual');
404  }
405 
406  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_none');
409  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_instantfb_or_followupqst');
410  } elseif ($this->getLockAnswerOnInstantFeedbackEnabled()) {
411  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_instant_feedback');
412  } elseif ($this->getLockAnswerOnNextQuestionEnabled()) {
413  $lock_answers = $additional_info->getTagForLangVar('tst_answer_fixation_on_followup_question');
414  }
416 
417  return $log_array;
418  }
419 
420  public function getQuestionTitleOutputMode(): int
421  {
422  return $this->question_title_output_mode;
423  }
424 
425  public function withQuestionTitleOutputMode(int $question_title_output_mode): self
426  {
427  $clone = clone $this;
428  $clone->question_title_output_mode = $question_title_output_mode;
429  return $clone;
430  }
431 
432  public function getAutosaveEnabled(): bool
433  {
434  return $this->autosave_enabled;
435  }
436 
437  public function withAutosaveEnabled(bool $autosave_enabled): self
438  {
439  $clone = clone $this;
440  $clone->autosave_enabled = $autosave_enabled;
441  return $clone;
442  }
443 
444  public function getAutosaveInterval(): int
445  {
446  return $this->autosave_interval;
447  }
448 
449  public function withAutosaveInterval(int $autosave_interval): self
450  {
451  $clone = clone $this;
452  $clone->autosave_interval = $autosave_interval;
453  return $clone;
454  }
455 
456  public function getShuffleQuestions(): bool
457  {
458  return $this->shuffle_questions;
459  }
460 
461  public function withShuffleQuestions(bool $shuffle_questions): self
462  {
463  $clone = clone $this;
464  $clone->shuffle_questions = $shuffle_questions;
465  return $clone;
466  }
467 
468  public function getQuestionHintsEnabled(): bool
469  {
470  return $this->question_hints_enabled;
471  }
472 
473  public function withQuestionHintsEnabled(bool $question_hints_enabled): self
474  {
475  $clone = clone $this;
476  $clone->question_hints_enabled = $question_hints_enabled;
477  return $clone;
478  }
479 
480  public function getInstantFeedbackPointsEnabled(): bool
481  {
482  return $this->instant_feedback_points_enabled;
483  }
484 
485  public function withInstantFeedbackPointsEnabled(bool $instant_feedback_points_enabled): self
486  {
487  $clone = clone $this;
488  $clone->instant_feedback_points_enabled = $instant_feedback_points_enabled;
489  return $clone;
490  }
491 
492  public function getInstantFeedbackGenericEnabled(): bool
493  {
494  return $this->instant_feedback_generic_enabled;
495  }
496 
497  public function withInstantFeedbackGenericEnabled(bool $instant_feedback_generic_enabled): self
498  {
499  $clone = clone $this;
500  $clone->instant_feedback_generic_enabled = $instant_feedback_generic_enabled;
501  return $clone;
502  }
503 
504  public function getInstantFeedbackSpecificEnabled(): bool
505  {
506  return $this->instant_feedback_specific_enabled;
507  }
508 
509  public function withInstantFeedbackSpecificEnabled(bool $instant_feedback_specific_enabled): self
510  {
511  $clone = clone $this;
512  $clone->instant_feedback_specific_enabled = $instant_feedback_specific_enabled;
513  return $clone;
514  }
515 
516  public function getInstantFeedbackSolutionEnabled(): bool
517  {
518  return $this->instant_feedback_solution_enabled;
519  }
520 
521  public function withInstantFeedbackSolutionEnabled(bool $instant_feedback_solution_enabled): self
522  {
523  $clone = clone $this;
524  $clone->instant_feedback_solution_enabled = $instant_feedback_solution_enabled;
525  return $clone;
526  }
527 
528  private function isAnyInstantFeedbackOptionEnabled(): bool
529  {
530  return $this->getInstantFeedbackPointsEnabled()
534  }
535 
537  {
538  return $this->force_instant_feedback_on_next_question;
539  }
540 
541  public function withForceInstantFeedbackOnNextQuestion(bool $force_instant_feedback_on_next_question): self
542  {
543  $clone = clone $this;
544  $clone->force_instant_feedback_on_next_question = $force_instant_feedback_on_next_question;
545  return $clone;
546  }
547 
549  {
550  return $this->lock_answer_on_instant_feedback;
551  }
552 
553  public function withLockAnswerOnInstantFeedbackEnabled(bool $lock_answer_on_instant_feedback): self
554  {
555  $clone = clone $this;
556  $clone->lock_answer_on_instant_feedback = $lock_answer_on_instant_feedback;
557  return $clone;
558  }
559 
560  public function getLockAnswerOnNextQuestionEnabled(): bool
561  {
562  return $this->lock_answer_on_next_question;
563  }
564 
565  public function withLockAnswerOnNextQuestionEnabled(bool $lock_answer_on_next_question): self
566  {
567  $clone = clone $this;
568  $clone->lock_answer_on_next_question = $lock_answer_on_next_question;
569  return $clone;
570  }
571 
572  private function getAnswerFixationSettingsAsFormValue(): string
573  {
576  return self::ANSWER_FIXATION_ON_IFB_OR_FUQST;
577  }
578 
580  return self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK;
581  }
582 
583  if ($this->getLockAnswerOnNextQuestionEnabled()) {
584  return self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION;
585  }
586 
587  return self::ANSWER_FIXATION_NONE;
588  }
589 }
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)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
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.
__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: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)