ILIAS  release_8 Revision v8.24
class.ilAssQuestionPreviewGUI.php
Go to the documentation of this file.
1<?php
2
19use ILIAS\Refinery\Random\Group as RandomGroup;
24
39{
40 public const CMD_SHOW = 'show';
41 public const CMD_RESET = 'reset';
42 public const CMD_STATISTICS = 'assessment';
43 public const CMD_INSTANT_RESPONSE = 'instantResponse';
44 public const CMD_HANDLE_QUESTION_ACTION = 'handleQuestionAction';
45 public const CMD_GATEWAY_CONFIRM_HINT_REQUEST = 'gatewayConfirmHintRequest';
46 public const CMD_GATEWAY_SHOW_HINT_LIST = 'gatewayShowHintList';
47
48 public const TAB_ID_QUESTION = 'question';
49
50 public const FEEDBACK_FOCUS_ANCHOR = 'focus';
51
53
66 private RandomGroup $randomGroup;
67
68 private int $parent_ref_id;
69
70 public function __construct(
78 RandomGroup $randomGroup,
81 ) {
82 $this->ctrl = $ctrl;
83 $this->rbac_system = $rbac_system;
84 $this->tabs = $tabs;
85 $this->tpl = $tpl;
86 $this->lng = $lng;
87 $this->db = $db;
88 $this->user = $user;
89 $this->randomGroup = $randomGroup;
90 $this->rbac_services = $rbac_services;
91 $this->parent_ref_id = $parent_ref_id;
92
93 $this->tpl->addCss(ilObjStyleSheet::getContentStylePath(0));
94 $this->tpl->addCss(ilObjStyleSheet::getSyntaxStylePath());
95 }
96
97 public function initQuestion($questionId, $parentObjId): void
98 {
99 $this->questionGUI = assQuestion::instantiateQuestionGUI($questionId);
100 $this->questionOBJ = $this->questionGUI->object;
101
102 $this->questionOBJ->setObjId($parentObjId);
103
104 if ($this->ctrl->getCmd() === 'editQuestion') {
105 $this->questionGUI->setQuestionTabs();
106 } else {
107 if ($_GET["q_id"]) {
108 $this->tabs->clearTargets();
109 $this->tabs->addTarget(
110 self::TAB_ID_QUESTION,
111 $this->ctrl->getLinkTargetByClass('ilAssQuestionPreviewGUI', self::CMD_SHOW),
112 '',
113 [strtolower(__CLASS__)]
114 );
115 // Assessment of questions sub menu entry
116 $q_type = $this->questionOBJ->getQuestionType();
117 $classname = $q_type . "GUI";
118 $this->tabs->addTarget(
119 "statistics",
120 $this->ctrl->getLinkTargetByClass('ilAssQuestionPreviewGUI', "assessment"),
121 array("assessment"),
122 $classname,
123 ""
124 );
125 if ((isset($_GET['calling_test']) && strlen($_GET['calling_test']) !== 0) ||
126 (isset($_GET['test_ref_id']) && strlen($_GET['test_ref_id']) !== 0)) {
127 $ref_id = $_GET['calling_test'];
128 if (strlen($ref_id) !== 0 && !is_numeric($ref_id)) {
129 $ref_id_array = explode('_', $ref_id);
130 $ref_id = array_pop($ref_id_array);
131 }
132
133 if (strlen($ref_id) === 0) {
134 $ref_id = $_GET['test_ref_id'];
135 }
136
138 $this->tabs->setBackTarget(
139 $this->lng->txt("backtocallingtest"),
140 "ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=$ref_id"
141 );
142 } elseif (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer']) {
143 $ref_id = (int) $_GET['calling_consumer'];
145 if ($consumer instanceof ilQuestionEditingFormConsumer) {
146 $this->tabs->setBackTarget(
147 $consumer->getQuestionEditingFormBackTargetLabel(),
148 $consumer->getQuestionEditingFormBackTarget($_GET['consumer_context'])
149 );
150 } else {
151 $this->tabs->setBackTarget($this->lng->txt("qpl"), ilLink::_getLink($ref_id));
152 }
153 } else {
154 $this->ctrl->clearParameterByClass(ilObjQuestionPoolGUI::class, 'q_id');
155 $this->tabs->setBackTarget($this->lng->txt("backtocallingpool"), $this->ctrl->getLinkTargetByClass(ilObjQuestionPoolGUI::class, "questions"));
156 $this->ctrl->setParameterByClass(ilObjQuestionPoolGUI::class, 'q_id', $questionId);
157 }
158 }
159 }
160 $this->questionGUI->outAdditionalOutput();
161
162 $this->questionGUI->populateJavascriptFilesRequiredForWorkForm($this->tpl);
163 $this->questionOBJ->setOutputType(OUTPUT_JAVASCRIPT); // TODO: remove including depending stuff
164
165 $this->questionGUI->setTargetGui($this);
166 $this->questionGUI->setQuestionActionCmd(self::CMD_HANDLE_QUESTION_ACTION);
167
168 $this->questionGUI->setRenderPurpose(assQuestionGUI::RENDER_PURPOSE_DEMOPLAY);
169 }
170
171 public function getObject(): assQuestion
172 {
173 return $this->questionOBJ;
174 }
175
176 public function initPreviewSettings($parentRefId): void
177 {
178 $this->previewSettings = new ilAssQuestionPreviewSettings($parentRefId);
179
180 $this->previewSettings->init();
181 }
182
183 public function initPreviewSession($userId, $questionId): void
184 {
185 $this->previewSession = new ilAssQuestionPreviewSession($userId, $questionId);
186
187 $this->previewSession->init();
188 }
189
190 public function initHintTracking(): void
191 {
192 $this->hintTracking = new ilAssQuestionPreviewHintTracking($this->db, $this->previewSession);
193 }
194
195 public function initStyleSheets(): void
196 {
197 $this->tpl->setCurrentBlock("ContentStyle");
198 $this->tpl->setVariable("LOCATION_CONTENT_STYLESHEET", ilObjStyleSheet::getContentStylePath(0));
199 $this->tpl->parseCurrentBlock();
200
201 $this->tpl->setCurrentBlock("SyntaxStyle");
202 $this->tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", ilObjStyleSheet::getSyntaxStylePath());
203 $this->tpl->parseCurrentBlock();
204 }
205
206 public function executeCommand(): void
207 {
208 global $DIC;
209 $ilHelp = $DIC['ilHelp'];
210 $ilHelp->setScreenIdComponent('qpl');
211
212 $this->tabs->setTabActive(self::TAB_ID_QUESTION);
213
214 $this->lng->loadLanguageModule('content');
215
216 $nextClass = $this->ctrl->getNextClass($this);
217
218 switch ($nextClass) {
219 case 'ilassquestionhintrequestgui':
220 $gui = new ilAssQuestionHintRequestGUI($this, self::CMD_SHOW, $this->questionGUI, $this->hintTracking);
221 $this->ctrl->forwardCommand($gui);
222 break;
223 case 'ilassspecfeedbackpagegui':
224 case 'ilassgenfeedbackpagegui':
225 $forwarder = new ilAssQuestionFeedbackPageObjectCommandForwarder($this->questionOBJ, $this->ctrl, $this->tabs, $this->lng);
226 $forwarder->forward();
227 break;
228 case 'ilnotegui':
229 $notesGUI = new ilNoteGUI($this->questionOBJ->getObjId(), $this->questionOBJ->getId(), 'quest');
230 $notesGUI->enablePublicNotes(true);
231 $notesGUI->enablePublicNotesDeletion(true);
232 $notesPanelHTML = $this->ctrl->forwardCommand($notesGUI);
233 $this->showCmd($notesPanelHTML);
234 break;
235 default:
236 $cmd = $this->ctrl->getCmd(self::CMD_SHOW) . 'Cmd';
237 $this->$cmd();
238 }
239 }
240
244 protected function buildPreviewFormAction(): string
245 {
246 return $this->ctrl->getFormAction($this, self::CMD_SHOW) . '#' . self::FEEDBACK_FOCUS_ANCHOR;
247 }
248
249 protected function isCommentingRequired(): bool
250 {
251 if ($this->previewSettings->isTestRefId()) {
252 return false;
253 }
254
255 return (bool) $this->rbac_services->system()->checkAccess('write', (int) $_GET['ref_id']);
256 }
257
258 private function showCmd($notesPanelHTML = ''): void
259 {
260 $tpl = new ilTemplate('tpl.qpl_question_preview.html', true, true, 'Modules/TestQuestionPool');
261 $tpl->setVariable('PREVIEW_FORMACTION', $this->buildPreviewFormAction());
262
263 $this->populatePreviewToolbar($tpl);
264 $this->populateQuestionOutput($tpl);
266
267 if ($this->isCommentingRequired()) {
268 $this->populateNotesPanel($tpl, $notesPanelHTML);
269 }
270
271 $this->tpl->setContent($tpl->get());
272 }
273
274 private function assessmentCmd()
275 {
276 $this->tabs->activateTab('statistics');
277 $this->questionGUI->assessment();
278 }
279
281 {
282 $response_required = false;
283 $response_available = false;
284 $jump_to_response = false;
285
286 if ($this->isShowReachedPointsRequired()) {
287 $this->populateReachedPointsOutput($tpl);
288 $response_required = true;
289 $response_available = true;
290 $jump_to_response = true;
291 }
292
293 if ($this->isShowBestSolutionRequired()) {
294 $this->populateSolutionOutput($tpl);
295 $response_required = true;
296 $response_available = true;
297 $jump_to_response = true;
298 }
299
301 $response_required = true;
302 if ($this->populateGenericQuestionFeedback($tpl)) {
303 $response_available = true;
304 $jump_to_response = true;
305 }
306 }
307
309 $response_required = true;
310
311 if ($this->questionGUI->hasInlineFeedback()) {
312 // Don't jump to the feedback below the question if some feedback is shown within the question
313 $jump_to_response = false;
314 } else {
315 if ($this->populateSpecificQuestionFeedback($tpl)) {
316 $response_available = true;
317 $jump_to_response = true;
318 }
319 }
320 }
321
322 if ($response_required) {
323 $this->populateInstantResponseHeader($tpl, $jump_to_response);
324 if (!$response_available) {
325 if ($this->questionGUI->hasInlineFeedback()) {
327 $tpl,
328 $this->lng->txt('tst_feedback_is_given_inline')
329 );
330 } else {
332 $tpl,
333 $this->lng->txt('tst_feedback_not_available_for_answer')
334 );
335 }
336 }
337 }
338 }
339
340 private function resetCmd(): void
341 {
342 $this->previewSession->setRandomizerSeed(null);
343 $this->previewSession->setParticipantsSolution(null);
344 $this->previewSession->resetRequestedHints();
345 $this->previewSession->setInstantResponseActive(false);
346
347 $this->tpl->setOnScreenMessage('info', $this->lng->txt('qst_preview_reset_msg'), true);
348
349 $this->ctrl->redirect($this, self::CMD_SHOW);
350 }
351
352 private function instantResponseCmd(): void
353 {
354 if ($this->saveQuestionSolution()) {
355 $this->previewSession->setInstantResponseActive(true);
356 } else {
357 $this->previewSession->setInstantResponseActive(false);
358 }
359
360 $this->ctrl->redirect($this, self::CMD_SHOW);
361 }
362
363 private function handleQuestionActionCmd(): void
364 {
365 $this->questionOBJ->persistPreviewState($this->previewSession);
366 $this->ctrl->redirect($this, self::CMD_SHOW);
367 }
368
369 private function populatePreviewToolbar(ilTemplate $tpl): void
370 {
371 $toolbarGUI = new ilAssQuestionPreviewToolbarGUI($this->lng);
372
373 $toolbarGUI->setFormAction($this->ctrl->getFormAction($this, self::CMD_SHOW));
374 $toolbarGUI->setResetPreviewCmd(self::CMD_RESET);
375
376 // Check Permissions first, some Toolbar Actions are only available for write access
377 if ($this->rbac_services->system()->checkAccess('write', (int) $_GET['ref_id'])) {
378 $toolbarGUI->setEditPageCmd(
379 $this->ctrl->getLinkTargetByClass('ilAssQuestionPageGUI', 'edit')
380 );
381
382 $toolbarGUI->setEditQuestionCmd(
383 $this->ctrl->getLinkTargetByClass(
384 array('ilrepositorygui','ilobjquestionpoolgui', get_class($this->questionGUI)),
385 'editQuestion'
386 )
387 );
388 }
389
390 $toolbarGUI->build();
391
392 $tpl->setVariable('PREVIEW_TOOLBAR', $this->ctrl->getHTML($toolbarGUI));
393 }
394
395 private function populateQuestionOutput(ilTemplate $tpl): void
396 {
397 // FOR WHAT EXACTLY IS THIS USEFUL?
398 $this->ctrl->setReturnByClass('ilAssQuestionPageGUI', 'view');
399 $this->ctrl->setReturnByClass('ilObjQuestionPoolGUI', 'questions');
400
401 $pageGUI = new ilAssQuestionPageGUI($this->questionOBJ->getId());
402 $pageGUI->setRenderPageContainer(false);
403 $pageGUI->setEditPreview(true);
404 $pageGUI->setEnabledTabs(false);
405
406 // FOR WHICH SITUATION IS THIS WORKAROUND NECCESSARY? (sure .. imagemaps, but where this can be done?)
407 if (strlen($this->ctrl->getCmd()) == 0 && !isset($_POST['editImagemapForward_x'])) { // workaround for page edit imagemaps, keep in mind
408 $this->ctrl->setCmdClass(get_class($pageGUI));
409 $this->ctrl->setCmd('preview');
410 }
411
412 $this->questionGUI->setPreviewSession($this->previewSession);
413 $this->questionGUI->object->setShuffler($this->getQuestionAnswerShuffler());
414
415 $questionHtml = $this->questionGUI->getPreview(true, $this->isShowSpecificQuestionFeedbackRequired());
416 $this->questionGUI->magicAfterTestOutput();
417
418 $questionHtml .= $this->getQuestionNavigationHtml();
419
420 $pageGUI->setQuestionHTML(array($this->questionOBJ->getId() => $questionHtml));
421
422 $pageGUI->setPresentationTitle($this->questionOBJ->getTitleForHTMLOutput());
423
424 $tpl->setVariable('QUESTION_OUTPUT', $pageGUI->preview());
425 // \ilPageObjectGUI::preview sets an undefined tab, so the "question" tab has to be activated again
426 $this->tabs->setTabActive(self::TAB_ID_QUESTION);
427 }
428
430 {
431 $reachedPoints = $this->questionOBJ->calculateReachedPointsFromPreviewSession($this->previewSession);
432 $maxPoints = $this->questionOBJ->getMaximumPoints();
433
434 $scoreInformation = sprintf(
435 $this->lng->txt("you_received_a_of_b_points"),
436 $reachedPoints,
437 $maxPoints
438 );
439
440 $tpl->setCurrentBlock("reached_points_feedback");
441 $tpl->setVariable("REACHED_POINTS_FEEDBACK", $scoreInformation);
443 }
444
445 private function populateSolutionOutput(ilTemplate $tpl): void
446 {
447 // FOR WHAT EXACTLY IS THIS USEFUL?
448 $this->ctrl->setReturnByClass('ilAssQuestionPageGUI', 'view');
449 $this->ctrl->setReturnByClass('ilObjQuestionPoolGUI', 'questions');
450
451 $pageGUI = new ilAssQuestionPageGUI($this->questionOBJ->getId());
452
453 $pageGUI->setEditPreview(true);
454 $pageGUI->setEnabledTabs(false);
455
456 // FOR WHICH SITUATION IS THIS WORKAROUND NECCESSARY? (sure .. imagemaps, but where this can be done?)
457 if (strlen($this->ctrl->getCmd()) == 0 && !isset($_POST['editImagemapForward_x'])) { // workaround for page edit imagemaps, keep in mind
458 $this->ctrl->setCmdClass(get_class($pageGUI));
459 $this->ctrl->setCmd('preview');
460 }
461
462 $this->questionGUI->setPreviewSession($this->previewSession);
463
464 $pageGUI->setQuestionHTML(array($this->questionOBJ->getId() => $this->questionGUI->getSolutionOutput(0, null, false, false, true, false, true, false, false)));
465
466 $output = $this->questionGUI->getSolutionOutput(0, null, false, false, true, false, true, false, false);
467
468 $tpl->setCurrentBlock('solution_output');
469 $tpl->setVariable('TXT_CORRECT_SOLUTION', $this->lng->txt('tst_best_solution_is'));
470 $tpl->setVariable('SOLUTION_OUTPUT', $output);
472 }
473
474 private function getQuestionNavigationHtml(): string
475 {
476 $navGUI = new ilAssQuestionRelatedNavigationBarGUI($this->ctrl, $this->lng);
477
478 $navGUI->setInstantResponseCmd(self::CMD_INSTANT_RESPONSE);
479 $navGUI->setHintRequestCmd(self::CMD_GATEWAY_CONFIRM_HINT_REQUEST);
480 $navGUI->setHintListCmd(self::CMD_GATEWAY_SHOW_HINT_LIST);
481
482 $navGUI->setInstantResponseEnabled($this->previewSettings->isInstantFeedbackNavigationRequired());
483 $navGUI->setHintProvidingEnabled($this->previewSettings->isHintProvidingEnabled());
484
485 $navGUI->setHintRequestsPossible($this->hintTracking->requestsPossible());
486 $navGUI->setHintRequestsExist($this->hintTracking->requestsExist());
487
488 return $this->ctrl->getHTML($navGUI);
489 }
490
496 {
497 if ($this->questionOBJ->isPreviewSolutionCorrect($this->previewSession)) {
498 $feedback = $this->questionGUI->getGenericFeedbackOutputForCorrectSolution();
500 } else {
501 $feedback = $this->questionGUI->getGenericFeedbackOutputForIncorrectSolution();
503 }
504
505 if ($feedback !== '') {
506 $tpl->setCurrentBlock('instant_feedback_generic');
507 $tpl->setVariable('GENERIC_FEEDBACK', $feedback);
508 $tpl->setVariable('ILC_FB_CSS_CLASS', $cssClass);
510 return true;
511 }
512 return false;
513 }
514
520 {
521 $fb = $this->questionGUI->getSpecificFeedbackOutput(
522 (array) $this->previewSession->getParticipantsSolution()
523 );
524
525 if (!empty($fb)) {
526 $tpl->setCurrentBlock('instant_feedback_specific');
527 $tpl->setVariable('ANSWER_FEEDBACK', $fb);
529 return true;
530 }
531 return false;
532 }
533
534 protected function populateInstantResponseHeader(ilTemplate $tpl, $withFocusAnchor): void
535 {
536 if ($withFocusAnchor) {
537 $tpl->setCurrentBlock('inst_resp_id');
538 $tpl->setVariable('INSTANT_RESPONSE_FOCUS_ID', self::FEEDBACK_FOCUS_ANCHOR);
540 }
541
542 $tpl->setCurrentBlock('instant_response_header');
543 $tpl->setVariable('INSTANT_RESPONSE_HEADER', $this->lng->txt('tst_feedback'));
544 $tpl->parseCurrentBlock();
545 }
546
547 protected function populateInstantResponseMessage(ilTemplate $tpl, string $a_message)
548 {
549 $tpl->setCurrentBlock('instant_response_message');
550 $tpl->setVariable('INSTANT_RESPONSE_MESSAGE', $a_message);
552 }
553
554 private function isShowBestSolutionRequired()
555 {
556 if (!$this->previewSettings->isBestSolutionEnabled()) {
557 return false;
558 }
559
560 return $this->previewSession->isInstantResponseActive();
561 }
562
564 {
565 if (!$this->previewSettings->isGenericFeedbackEnabled()) {
566 return false;
567 }
568
569 return $this->previewSession->isInstantResponseActive();
570 }
571
573 {
574 if (!$this->previewSettings->isSpecificFeedbackEnabled()) {
575 return false;
576 }
577
578 return $this->previewSession->isInstantResponseActive();
579 }
580
582 {
583 if (!$this->previewSettings->isReachedPointsEnabled()) {
584 return false;
585 }
586
587 return $this->previewSession->isInstantResponseActive();
588 }
589
590 public function saveQuestionSolution(): bool
591 {
592 return $this->questionOBJ->persistPreviewState($this->previewSession);
593 }
594
595 public function gatewayConfirmHintRequestCmd(): void
596 {
597 if (!$this->saveQuestionSolution()) {
598 $this->previewSession->setInstantResponseActive(false);
599 $this->showCmd();
600 return;
601 }
602
603 $this->ctrl->redirectByClass(
604 'ilAssQuestionHintRequestGUI',
606 );
607 }
608
609 public function gatewayShowHintListCmd(): void
610 {
611 if (!$this->saveQuestionSolution()) {
612 $this->previewSession->setInstantResponseActive(false);
613 $this->showCmd();
614 return;
615 }
616
617 $this->ctrl->redirectByClass(
618 'ilAssQuestionHintRequestGUI',
620 );
621 }
622
627 {
628 if (!$this->previewSession->randomizerSeedExists()) {
629 $this->previewSession->setRandomizerSeed((new RandomSeed())->createSeed());
630 }
631 return $this->randomGroup->shuffleArray(new GivenSeed((int) $this->previewSession->getRandomizerSeed()));
632 }
633
634 protected function populateNotesPanel(ilTemplate $tpl, $notesPanelHTML): void
635 {
636 if (!strlen($notesPanelHTML)) {
637 $notesPanelHTML = $this->questionGUI->getNotesHTML();
638 }
639
640 $tpl->setCurrentBlock('notes_panel');
641 $tpl->setVariable('NOTES_PANEL', $notesPanelHTML);
643 }
644}
setVariable($variable, $value='')
Sets a variable value.
Definition: IT.php:514
Provides fluid interface to RBAC services.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Abstract basic class which is to be extended by the concrete assessment question type classes.
static instantiateQuestionGUI(int $a_question_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
populateInstantResponseHeader(ilTemplate $tpl, $withFocusAnchor)
populateSpecificQuestionFeedback(ilTemplate $tpl)
Populate the block for an instant specific feedback.
populateInstantResponseMessage(ilTemplate $tpl, string $a_message)
populateGenericQuestionFeedback(ilTemplate $tpl)
Populate the block for an instant generic feedback.
initQuestion($questionId, $parentObjId)
ilAssQuestionPreviewHintTracking $hintTracking
ilAssQuestionPreviewSettings $previewSettings
__construct(ilCtrl $ctrl, ilRbacSystem $rbac_system, ilTabsGUI $tabs, ilGlobalTemplateInterface $tpl, ilLanguage $lng, ilDBInterface $db, ilObjUser $user, RandomGroup $randomGroup, int $parent_ref_id, RBACServices $rbac_services)
populateNotesPanel(ilTemplate $tpl, $notesPanelHTML)
ilAssQuestionPreviewSession $previewSession
Class ilCtrl provides processing control methods.
language handling
Notes GUI class.
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
User class.
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
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
parseCurrentBlock(string $part=ilGlobalTemplateInterface::DEFAULT_BLOCK)
static getReturnToPageLink($q_id=null)
global $DIC
Definition: feed.php:28
const OUTPUT_JAVASCRIPT
A transformation is a function from one datatype to another.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface ilDBInterface.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setCurrentBlock(string $part=self::DEFAULT_BLOCK)
Sets the template to the given block.
parseCurrentBlock(string $block_name=self::DEFAULT_BLOCK)
Parses the given block.
setVariable(string $variable, $value='')
Sets the given variable to the given value.
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$ref_id
Definition: ltiauth.php:67
$_GET['client_id']
Definition: saml1-acs.php:21