ILIAS  trunk Revision v11.0_alpha-1723-g8e69f309bab
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
class.ilAssQuestionPreviewGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
25 use ILIAS\Refinery\Random\Group as RandomGroup;
31 
46 {
47  public const CMD_SHOW = 'show';
48  public const CMD_RESET = 'reset';
49  public const CMD_STATISTICS = 'assessment';
50  public const CMD_INSTANT_RESPONSE = 'instantResponse';
51  public const CMD_HANDLE_QUESTION_ACTION = 'handleQuestionAction';
52  public const CMD_GATEWAY_CONFIRM_HINT_REQUEST = 'gatewayConfirmHintRequest';
53  public const CMD_GATEWAY_SHOW_HINT_LIST = 'gatewayShowHintList';
54 
55  public const TAB_ID_QUESTION = 'question';
56 
57  public const FEEDBACK_FOCUS_ANCHOR = 'focus';
58 
60 
66 
67  private ?string $info_message = null;
68 
72  private array $primary_cmd = [];
73 
77  private array $additional_cmds = [];
78 
79  public function __construct(
80  private readonly ilCtrl $ctrl,
81  private readonly ilRbacSystem $rbac_system,
82  private ilTabsGUI $tabs,
83  private ilToolbarGUI $toolbar,
84  private ilGlobalTemplateInterface $tpl,
85  private readonly UIFactory $ui_factory,
86  private readonly ilLanguage $lng,
87  private readonly ilDBInterface $db,
88  private readonly RandomGroup $random_group,
89  private readonly GlobalScreen $global_screen,
90  private readonly HTTPServices $http,
91  private readonly Refinery $refinery,
92  private readonly int $parent_obj_ref_id
93  ) {
94  $this->tpl->addCss(ilObjStyleSheet::getContentStylePath(0));
95  $this->tpl->addCss(ilObjStyleSheet::getSyntaxStylePath());
96 
97  $local_dic = QuestionPoolDIC::dic();
98  $this->request_data_collector = $local_dic['request_data_collector'];
99  }
100 
101  public function setInfoMessage(string $message): void
102  {
103  $this->info_message = $message;
104  }
105 
106  public function setPrimaryCmd(string $label, string $cmd): void
107  {
108  $this->primary_cmd[$label] = $cmd;
109  }
110 
111  public function addAdditionalCmd(string $label, string $cmd): void
112  {
113  $this->additional_cmds[$label] = $cmd;
114  }
115 
116  public function getQuestion(): assQuestion
117  {
118  return $this->question_obj;
119  }
120 
121  public function getObject(): assQuestion
122  {
123  return $this->question_obj;
124  }
125 
126  public function initQuestion(assQuestionGUI $question_gui, int $parent_obj_id): void
127  {
128  $this->question_gui = $question_gui;
129  $this->question_obj = $this->question_gui->getObject();
130 
131  $this->question_obj->setObjId($parent_obj_id);
132 
133  $this->tabs->clearTargets();
134  $this->tabs->addTarget(
135  self::TAB_ID_QUESTION,
136  $this->ctrl->getLinkTargetByClass(self::class, self::CMD_SHOW),
137  '',
138  [strtolower(__CLASS__)]
139  );
140  // Assessment of questions sub menu entry
141  $q_type = $this->question_obj->getQuestionType();
142  $classname = $q_type . 'GUI';
143  $this->tabs->addTarget(
144  'statistics',
145  $this->ctrl->getLinkTargetByClass(self::class, 'assessment'),
146  ['assessment'],
147  $classname,
148  ''
149  );
150 
151  $this->question_gui->populateJavascriptFilesRequiredForWorkForm($this->tpl);
152  $this->question_gui->setTargetGui($this);
153  $this->question_gui->setQuestionActionCmd(self::CMD_HANDLE_QUESTION_ACTION);
154 
155  $this->question_gui->setRenderPurpose(assQuestionGUI::RENDER_PURPOSE_DEMOPLAY);
156  }
157 
158  public function initPreviewSettings(int $parent_ref_id): void
159  {
160  $this->preview_settings = new ilAssQuestionPreviewSettings($parent_ref_id);
161 
162  $this->preview_settings->init();
163  }
164 
165  public function initPreviewSession(int $user_id, int $question_id): void
166  {
167  $this->preview_session = new ilAssQuestionPreviewSession($user_id, $question_id);
168  $this->preview_session->init();
169  }
170 
171  public function initHintTracking(): void
172  {
173  $this->hint_tracking = new ilAssQuestionPreviewHintTracking($this->db, $this->preview_session);
174  }
175 
176  public function initStyleSheets(): void
177  {
178  $this->tpl->setCurrentBlock('ContentStyle');
179  $this->tpl->setVariable('LOCATION_CONTENT_STYLESHEET', ilObjStyleSheet::getContentStylePath(0));
180  $this->tpl->parseCurrentBlock();
181 
182  $this->tpl->setCurrentBlock('SyntaxStyle');
183  $this->tpl->setVariable('LOCATION_SYNTAX_STYLESHEET', ilObjStyleSheet::getSyntaxStylePath());
184  $this->tpl->parseCurrentBlock();
185  }
186 
187  public function executeCommand(): void
188  {
189  global $DIC;
190  $ilHelp = $DIC['ilHelp'];
191  $ilHelp->setScreenIdComponent('qpl');
192 
193  $this->tabs->setTabActive(self::TAB_ID_QUESTION);
194 
195  $this->lng->loadLanguageModule('content');
196 
197  $nextClass = $this->ctrl->getNextClass($this);
198 
199  switch ($nextClass) {
200  case 'ilassquestionhintrequestgui':
201  $gui = new ilAssQuestionHintRequestGUI(
202  $this,
203  self::CMD_SHOW,
204  $this->question_gui,
205  $this->hint_tracking,
206  $this->ctrl,
207  $this->lng,
208  $this->tpl,
209  $this->tabs,
210  $this->global_screen
211  );
212  $this->ctrl->forwardCommand($gui);
213  break;
214  case 'ilassspecfeedbackpagegui':
215  case 'ilassgenfeedbackpagegui':
216  $forwarder = new ilAssQuestionFeedbackPageObjectCommandForwarder($this->question_obj, $this->ctrl, $this->tabs, $this->lng);
217  $forwarder->forward();
218  break;
219  case 'ilcommentgui':
220  $comment_gui = new ilCommentGUI($this->question_obj->getObjId(), $this->question_obj->getId(), 'quest');
221  $comments_panel_html = $this->ctrl->forwardCommand($comment_gui);
222  $this->showCmd($comments_panel_html);
223  break;
224  default:
225  $cmd = $this->ctrl->getCmd(self::CMD_SHOW);
226  $this->{$cmd . 'Cmd'}();
227  }
228  }
229 
230  protected function buildPreviewFormAction(): string
231  {
232  return $this->ctrl->getFormAction($this, self::CMD_SHOW) . '#' . self::FEEDBACK_FOCUS_ANCHOR;
233  }
234 
235  protected function isCommentingRequired(): bool
236  {
237  return !$this->preview_settings->isTestRefId() &&
238  $this->rbac_system->checkAccess('read', $this->request_data_collector->getRefId());
239  }
240 
241  public function showCmd(string $commands_panel_html = ''): void
242  {
243  $tpl = new ilTemplate('tpl.qpl_question_preview.html', true, true, 'components/ILIAS/TestQuestionPool');
244  $tpl->setVariable('PREVIEW_FORMACTION', $this->buildPreviewFormAction());
245 
246  $modal = '';
247  if ($this->question_gui->isSaveCommand() && $this->question_gui->needsSyncQuery()) {
248  $modal = $this->question_gui->getQuestionSyncModal(assQuestionGUI::CMD_SYNC_QUESTION_AND_RETURN);
249  }
250 
251  if ($this->info_message !== null) {
252  $this->tpl->setOnScreenMessage('info', $this->info_message, true);
253  }
254 
255  $this->populateToolbar();
256  $this->populateQuestionOutput($tpl);
257  $this->handleInstantResponseRendering($tpl);
258 
259  if ($this->isCommentingRequired()) {
260  $this->populateCommentsPanel($tpl, $commands_panel_html);
261  }
262 
263  $this->tpl->setContent($tpl->get() . $modal);
264  }
265 
266  public function assessmentCmd()
267  {
268  $this->tabs->activateTab('statistics');
269  $this->question_gui->assessment();
270  }
271 
272  public function handleInstantResponseRendering(ilTemplate $tpl): void
273  {
274  $response_required = false;
275  $response_available = false;
276  $jump_to_response = false;
277 
278  if ($this->isShowReachedPointsRequired()) {
279  $this->populateReachedPointsOutput($tpl);
280  $response_required = true;
281  $response_available = true;
282  $jump_to_response = true;
283  }
284 
285  if ($this->isShowBestSolutionRequired()) {
286  $this->populateSolutionOutput($tpl);
287  $response_required = true;
288  $response_available = true;
289  $jump_to_response = true;
290  }
291 
293  $response_required = true;
294  if ($this->populateGenericQuestionFeedback($tpl)) {
295  $response_available = true;
296  $jump_to_response = true;
297  }
298  }
299 
301  $response_required = true;
302 
303  if ($this->question_gui->hasInlineFeedback()) {
304  // Don't jump to the feedback below the question if some feedback is shown within the question
305  $jump_to_response = false;
306  } else {
307  if ($this->populateSpecificQuestionFeedback($tpl)) {
308  $response_available = true;
309  $jump_to_response = true;
310  }
311  }
312  }
313 
314  if ($response_required) {
315  $this->populateInstantResponseHeader($tpl, $jump_to_response);
316  if (!$response_available) {
317  if ($this->question_gui->hasInlineFeedback()) {
319  $tpl,
320  $this->lng->txt('tst_feedback_is_given_inline')
321  );
322  } else {
324  $tpl,
325  $this->lng->txt('tst_feedback_not_available_for_answer')
326  );
327  }
328  }
329  }
330  }
331 
332  public function resetCmd(): void
333  {
334  $this->preview_session->setRandomizerSeed(null);
335  $this->preview_session->setParticipantsSolution(null);
336  $this->preview_session->resetRequestedHints();
337  $this->preview_session->setInstantResponseActive(false);
338 
339  $this->tpl->setOnScreenMessage('info', $this->lng->txt('qst_preview_reset_msg'), true);
340 
341  $this->ctrl->redirect($this, self::CMD_SHOW);
342  }
343 
344  public function instantResponseCmd(): void
345  {
346  if ($this->saveQuestionSolution()) {
347  $this->preview_session->setInstantResponseActive(true);
348  } else {
349  $this->preview_session->setInstantResponseActive(false);
350  }
351 
352  $this->ctrl->redirect($this, self::CMD_SHOW);
353  }
354 
355  public function handleQuestionActionCmd(): void
356  {
357  $this->question_obj->persistPreviewState($this->preview_session);
358  $this->ctrl->redirect($this, self::CMD_SHOW);
359  }
360 
361  private function populateToolbar(): void
362  {
363  $this->toolbar->setFormAction($this->ctrl->getFormAction($this, self::CMD_SHOW));
364 
365  if ($this->rbac_system->checkAccess('write', $this->parent_obj_ref_id)) {
366  if ($this->primary_cmd !== []) {
367  $this->toolbar->addComponent(
368  $this->ui_factory->button()->primary(key($this->primary_cmd), current($this->primary_cmd))
369  );
370  }
371  foreach ($this->additional_cmds as $label => $action) {
372  $this->toolbar->addComponent(
373  $this->ui_factory->button()->standard($label, $action)
374  );
375  }
376  }
377 
378  $this->toolbar->addComponent(
379  $this->ui_factory->button()->standard(
380  $this->lng->txt('qpl_reset_preview'),
381  $this->ctrl->getLinkTargetByClass(ilAssQuestionPreviewGUI::class, self::CMD_RESET)
382  )
383  );
384  }
385 
386  private function populateQuestionOutput(ilTemplate $tpl): void
387  {
388  // FOR WHAT EXACTLY IS THIS USEFUL?
389  $this->ctrl->setReturnByClass('ilAssQuestionPageGUI', 'view');
390  $this->ctrl->setReturnByClass('ilObjQuestionPoolGUI', 'questions');
391 
392  $page_gui = new ilAssQuestionPageGUI($this->question_obj->getId());
393  $page_gui->setRenderPageContainer(false);
394  $page_gui->setEditPreview(true);
395  $page_gui->setEnabledTabs(false);
396 
397  $this->question_gui->setPreviewSession($this->preview_session);
398  $question = $this->question_gui->getObject();
399  $question->setShuffler($this->getQuestionAnswerShuffler());
400  $this->question_gui->setObject($question);
401 
402  $question_html = $this->question_gui->getPreview(true, $this->isShowSpecificQuestionFeedbackRequired());
403  $this->question_gui->magicAfterTestOutput();
404 
405  $question_html .= $this->getQuestionNavigationHtml();
406 
407  $page_gui->setQuestionHTML([$this->question_obj->getId() => $question_html]);
408 
409  $page_gui->setPresentationTitle($this->question_obj->getTitleForHTMLOutput());
410 
411  $tpl->setVariable('QUESTION_OUTPUT', $page_gui->preview());
412  // \ilPageObjectGUI::preview sets an undefined tab, so the "question" tab has to be activated again
413  $this->tabs->setTabActive(self::TAB_ID_QUESTION);
414  }
415 
416  protected function populateReachedPointsOutput(ilTemplate $tpl): void
417  {
418  $reachedPoints = $this->question_obj->calculateReachedPointsFromPreviewSession($this->preview_session);
419  $maxPoints = $this->question_obj->getMaximumPoints();
420 
421  $scoreInformation = sprintf(
422  $this->lng->txt("you_received_a_of_b_points"),
423  $reachedPoints,
424  $maxPoints
425  );
426 
427  $tpl->setCurrentBlock("reached_points_feedback");
428  $tpl->setVariable("REACHED_POINTS_FEEDBACK", $scoreInformation);
429  $tpl->parseCurrentBlock();
430  }
431 
432  private function populateSolutionOutput(ilTemplate $tpl): void
433  {
434  // FOR WHAT EXACTLY IS THIS USEFUL?
435  $this->ctrl->setReturnByClass('ilAssQuestionPageGUI', 'view');
436  $this->ctrl->setReturnByClass('ilObjQuestionPoolGUI', 'questions');
437 
438  $pageGUI = new ilAssQuestionPageGUI($this->question_obj->getId());
439 
440  $pageGUI->setEditPreview(true);
441  $pageGUI->setEnabledTabs(false);
442 
443  $this->question_gui->setPreviewSession($this->preview_session);
444 
445  $pageGUI->setQuestionHTML([$this->question_obj->getId() => $this->question_gui->getSolutionOutput(0, null, false, false, true, false, true, false, false)]);
446 
447  $output = $this->question_gui->getSolutionOutput(0, null, false, false, true, false, true, false, false);
448 
449  $tpl->setCurrentBlock('solution_output');
450  $tpl->setVariable('TXT_CORRECT_SOLUTION', $this->lng->txt('tst_best_solution_is'));
451  $tpl->setVariable('SOLUTION_OUTPUT', $output);
452  $tpl->parseCurrentBlock();
453  }
454 
455  private function getQuestionNavigationHtml(): string
456  {
457  $navGUI = new ilAssQuestionRelatedNavigationBarGUI($this->ctrl, $this->lng);
458 
459  $navGUI->setInstantResponseCmd(self::CMD_INSTANT_RESPONSE);
460  $navGUI->setHintRequestCmd(self::CMD_GATEWAY_CONFIRM_HINT_REQUEST);
461  $navGUI->setHintListCmd(self::CMD_GATEWAY_SHOW_HINT_LIST);
462 
463  $navGUI->setInstantResponseEnabled($this->preview_settings->isInstantFeedbackNavigationRequired());
464  $navGUI->setHintProvidingEnabled($this->preview_settings->isHintProvidingEnabled());
465 
466  $navGUI->setHintRequestsPossible($this->hint_tracking->requestsPossible());
467  $navGUI->setHintRequestsExist($this->hint_tracking->requestsExist());
468 
469  return $this->ctrl->getHTML($navGUI);
470  }
471 
476  private function populateGenericQuestionFeedback(ilTemplate $tpl): bool
477  {
478  if ($this->question_obj->isPreviewSolutionCorrect($this->preview_session)) {
479  $feedback = $this->question_gui->getGenericFeedbackOutputForCorrectSolution();
481  } else {
482  $feedback = $this->question_gui->getGenericFeedbackOutputForIncorrectSolution();
484  }
485 
486  if ($feedback !== '') {
487  $tpl->setCurrentBlock('instant_feedback_generic');
488  $tpl->setVariable('GENERIC_FEEDBACK', $feedback);
489  $tpl->setVariable('ILC_FB_CSS_CLASS', $cssClass);
490  $tpl->parseCurrentBlock();
491  return true;
492  }
493  return false;
494  }
495 
500  private function populateSpecificQuestionFeedback(ilTemplate $tpl): bool
501  {
502  $fb = $this->question_gui->getSpecificFeedbackOutput(
503  (array) $this->preview_session->getParticipantsSolution()
504  );
505 
506  if (!empty($fb)) {
507  $tpl->setCurrentBlock('instant_feedback_specific');
508  $tpl->setVariable('ANSWER_FEEDBACK', $fb);
509  $tpl->parseCurrentBlock();
510  return true;
511  }
512  return false;
513  }
514 
515  protected function populateInstantResponseHeader(ilTemplate $tpl, $withFocusAnchor): void
516  {
517  if ($withFocusAnchor) {
518  $tpl->setCurrentBlock('inst_resp_id');
519  $tpl->setVariable('INSTANT_RESPONSE_FOCUS_ID', self::FEEDBACK_FOCUS_ANCHOR);
520  $tpl->parseCurrentBlock();
521  }
522 
523  $tpl->setCurrentBlock('instant_response_header');
524  $tpl->setVariable('INSTANT_RESPONSE_HEADER', $this->lng->txt('tst_feedback'));
525  $tpl->parseCurrentBlock();
526  }
527 
528  protected function populateInstantResponseMessage(ilTemplate $tpl, string $a_message)
529  {
530  $tpl->setCurrentBlock('instant_response_message');
531  $tpl->setVariable('INSTANT_RESPONSE_MESSAGE', $a_message);
532  $tpl->parseCurrentBlock();
533  }
534 
535  private function isShowBestSolutionRequired()
536  {
537  if (!$this->preview_settings->isBestSolutionEnabled()) {
538  return false;
539  }
540 
541  return $this->preview_session->isInstantResponseActive();
542  }
543 
545  {
546  if (!$this->preview_settings->isGenericFeedbackEnabled()) {
547  return false;
548  }
549 
550  return $this->preview_session->isInstantResponseActive();
551  }
552 
553  private function isShowSpecificQuestionFeedbackRequired(): bool
554  {
555  if (!$this->preview_settings->isSpecificFeedbackEnabled()) {
556  return false;
557  }
558 
559  return $this->preview_session->isInstantResponseActive();
560  }
561 
562  private function isShowReachedPointsRequired()
563  {
564  if (!$this->preview_settings->isReachedPointsEnabled()) {
565  return false;
566  }
567 
568  return $this->preview_session->isInstantResponseActive();
569  }
570 
571  public function saveQuestionSolution(): bool
572  {
573  return $this->question_obj->persistPreviewState($this->preview_session);
574  }
575 
576  public function gatewayConfirmHintRequestCmd(): void
577  {
578  if (!$this->saveQuestionSolution()) {
579  $this->preview_session->setInstantResponseActive(false);
580  $this->showCmd();
581  return;
582  }
583 
584  $this->ctrl->redirectByClass(
585  'ilAssQuestionHintRequestGUI',
587  );
588  }
589 
590  public function gatewayShowHintListCmd(): void
591  {
592  if (!$this->saveQuestionSolution()) {
593  $this->preview_session->setInstantResponseActive(false);
594  $this->showCmd();
595  return;
596  }
597 
598  $this->ctrl->redirectByClass(
599  'ilAssQuestionHintRequestGUI',
601  );
602  }
603 
608  {
609  if (!$this->preview_session->randomizerSeedExists()) {
610  $this->preview_session->setRandomizerSeed((new RandomSeed())->createSeed());
611  }
612  return $this->random_group->shuffleArray(new GivenSeed((int) $this->preview_session->getRandomizerSeed()));
613  }
614 
615  protected function populateCommentsPanel(ilTemplate $tpl, string $comments_panel_html): void
616  {
617  if ($comments_panel_html === '') {
618  $comments_panel_html = $this->question_gui->getCommentsPanelHTML();
619  }
620 
621  $tpl->setCurrentBlock('notes_panel');
622  $tpl->setVariable('NOTES_PANEL', $comments_panel_html);
623  $tpl->parseCurrentBlock();
624  }
625 }
parseCurrentBlock(string $part=ilGlobalTemplateInterface::DEFAULT_BLOCK)
setEditPreview(bool $a_editpreview)
Set Display first Edit tab, then Preview tab, instead of Page and Edit.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
populateCommentsPanel(ilTemplate $tpl, string $comments_panel_html)
ilAssQuestionPreviewSettings $preview_settings
readonly RequestDataCollector $request_data_collector
__construct(private readonly ilCtrl $ctrl, private readonly ilRbacSystem $rbac_system, private ilTabsGUI $tabs, private ilToolbarGUI $toolbar, private ilGlobalTemplateInterface $tpl, private readonly UIFactory $ui_factory, private readonly ilLanguage $lng, private readonly ilDBInterface $db, private readonly RandomGroup $random_group, private readonly GlobalScreen $global_screen, private readonly HTTPServices $http, private readonly Refinery $refinery, private readonly int $parent_obj_ref_id)
ilAssQuestionPreviewSession $preview_session
initQuestion(assQuestionGUI $question_gui, int $parent_obj_id)
$http
Definition: deliver.php:30
initPreviewSession(int $user_id, int $question_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
ilAssQuestionPreviewHintTracking $hint_tracking
setVariable($variable, $value='')
Sets a variable value.
Definition: IT.php:544
setRenderPageContainer(bool $a_val)
global $DIC
Definition: shib_login.php:22
populateGenericQuestionFeedback(ilTemplate $tpl)
Populate the block for an instant generic feedback.
populateInstantResponseHeader(ilTemplate $tpl, $withFocusAnchor)
populateInstantResponseMessage(ilTemplate $tpl, string $a_message)
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
setCurrentBlock(string $part=ilGlobalTemplateInterface::DEFAULT_BLOCK)
Comment GUI.
global $lng
Definition: privfeed.php:31
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
A transformation is a function from one datatype to another.
$message
Definition: xapiexit.php:31
setPrimaryCmd(string $label, string $cmd)
showCmd(string $commands_panel_html='')
addAdditionalCmd(string $label, string $cmd)
populateSpecificQuestionFeedback(ilTemplate $tpl)
Populate the block for an instant specific feedback.