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