ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilAssQuestionPreviewGUI.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use ILIAS\HTTP\Services as HTTPServices;
23use ILIAS\UI\Factory as UIFactory;
24use ILIAS\Refinery\Factory as Refinery;
25use ILIAS\Refinery\Random\Group as RandomGroup;
29use ILIAS\GlobalScreen\Services as GlobalScreen;
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
59 private ?assQuestion $question_obj = null;
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') {
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);
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
543 {
544 if (!$this->preview_settings->isSpecificFeedbackEnabled()) {
545 return false;
546 }
547
548 return $this->preview_session->isInstantResponseActive();
549 }
550
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}
setVariable($variable, $value='')
Sets a variable value.
Definition: IT.php:544
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Builds data types.
Definition: Factory.php:36
Class Services.
Definition: Services.php:38
Question page GUI class.
populateInstantResponseHeader(ilTemplate $tpl, $withFocusAnchor)
populateSpecificQuestionFeedback(ilTemplate $tpl)
Populate the block for an instant specific feedback.
readonly RequestDataCollector $request_data_collector
populateInstantResponseMessage(ilTemplate $tpl, string $a_message)
populateGenericQuestionFeedback(ilTemplate $tpl)
Populate the block for an instant generic feedback.
showCmd(string $commands_panel_html='')
populateCommentsPanel(ilTemplate $tpl, string $comments_panel_html)
ilAssQuestionPreviewSettings $preview_settings
ilAssQuestionPreviewSession $preview_session
addAdditionalCmd(string $label, string $cmd)
initQuestion(assQuestionGUI $question_gui, int $parent_obj_id)
__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)
initPreviewSession(int $user_id, int $question_id)
setPrimaryCmd(string $label, string $cmd)
Class ilCtrl provides processing control methods.
language handling
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
Class ilPageObjectGUI.
class ilRbacSystem system function like checkAccess, addActiveRole ... Supporting system functions ar...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
special template class to simplify handling of ITX/PEAR
setCurrentBlock(string $part=ilGlobalTemplateInterface::DEFAULT_BLOCK)
parseCurrentBlock(string $part=ilGlobalTemplateInterface::DEFAULT_BLOCK)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$http
Definition: deliver.php:30
A transformation is a function from one datatype to another.
Interface ilDBInterface.
global $lng
Definition: privfeed.php:31
global $DIC
Definition: shib_login.php:26
$message
Definition: xapiexit.php:31