ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilTestCorrectionsGUI.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
27 
37 {
39 
45  public function __construct(
46  protected ilDBInterface $database,
47  protected ilCtrl $ctrl,
48  protected ilAccessHandler $access,
49  protected ilLanguage $language,
50  protected ilTabsGUI $tabs,
51  protected ilHelpGUI $help,
52  protected UIFactory $ui_factory,
53  protected UIRenderer $ui_renderer,
54  protected ilGlobalTemplateInterface $main_tpl,
55  protected RefineryFactory $refinery,
56  protected RequestInterface $request,
57  private InternalRequestService $testrequest,
58  protected ilObjTest $testOBJ,
59  protected QuestionInfoService $questioninfo
60  ) {
61  $this->testAccess = new ilTestAccess($testOBJ->getRefId(), $testOBJ->getTestId());
62  }
63 
64  public function executeCommand()
65  {
66  if (!$this->testAccess->checkCorrectionsAccess()) {
67  ilObjTestGUI::accessViolationRedirect();
68  }
69  if (
70  $this->testrequest->isset('eqid') && (int) $this->testrequest->raw('eqid')
71  && $this->testrequest->isset('eqpl') && (int) $this->testrequest->raw('eqpl')
72  ) {
73  $this->ctrl->setParameter($this, 'qid', (int) $this->testrequest->raw('eqid'));
74  $this->ctrl->redirect($this, 'showQuestion');
75  }
76  if ($this->testrequest->isset('removeQid') && (int) $this->testrequest->raw('removeQid')) {
77  $this->ctrl->setParameter($this, 'qid', (int) $this->testrequest->raw('removeQid'));
78  $this->ctrl->redirect($this, 'confirmQuestionRemoval');
79  }
80 
81  if ((int) $this->testrequest->raw('qid')
82  && !$this->checkQuestion((int) $this->testrequest->raw('qid'))) {
83  ilObjTestGUI::accessViolationRedirect();
84  }
85 
86  $this->ctrl->saveParameter($this, 'qid');
87 
88  switch ($this->ctrl->getNextClass($this)) {
89  default:
90 
91  $command = $this->ctrl->getCmd('showQuestionList');
92  $this->{$command}();
93  }
94  }
95 
96  protected function showQuestionList()
97  {
98  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_CORRECTION);
99 
100  if ($this->testOBJ->isFixedTest()) {
101  $table_gui = new ilTestQuestionsTableGUI(
102  $this,
103  'showQuestionList',
104  $this->testOBJ->getRefId(),
105  $this->access,
106  $this->ui_factory,
107  $this->ui_renderer,
108  $this->questioninfo
109  );
110 
111  $table_gui->setQuestionRemoveRowButtonEnabled(true);
112  $table_gui->init();
113 
114  $table_gui->setData($this->getQuestions());
115 
116  $rendered_gui_component = $table_gui->getHTML();
117  } else {
118  $lng = $this->language;
119  $txt = $lng->txt('tst_corrections_incompatible_question_set_type');
120 
121  $infoBox = $this->ui_factory->messageBox()->info($txt);
122 
123  $rendered_gui_component = $this->ui_renderer->render($infoBox);
124  }
125 
126  $this->main_tpl->setContent($rendered_gui_component);
127  }
128 
129  protected function showQuestion(ilPropertyFormGUI $form = null)
130  {
131  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
132 
133  $this->setCorrectionTabsContext($questionGUI, 'question');
134 
135  $form ??= $this->buildQuestionCorrectionForm($questionGUI);
136 
137  $this->populatePageTitleAndDescription($questionGUI);
138  $this->main_tpl->setContent($form->getHTML());
139  }
140 
141  protected function saveQuestion()
142  {
143  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
144 
145  $form = $this->buildQuestionCorrectionForm($questionGUI);
146 
147  $form->setValuesByPost();
148 
149  if (!$form->checkInput()) {
150  $questionGUI->prepareReprintableCorrectionsForm($form);
151 
152  $this->showQuestion($form);
153  return;
154  }
155 
156  $questionGUI->saveCorrectionsFormProperties($form);
157  $questionGUI->object->setPoints($questionGUI->object->getMaximumPoints());
158  $questionGUI->object->saveToDb();
159 
160  $scoring = new ilTestScoring($this->testOBJ, $this->database);
161  $scoring->setPreserveManualScores(false);
162  $scoring->setQuestionId($questionGUI->object->getId());
163  $scoring->recalculateSolutions();
164 
165  $this->main_tpl->setOnScreenMessage('success', $this->language->txt('saved_successfully'), true);
166  $this->ctrl->redirect($this, 'showQuestion');
167  }
168 
170  {
171  $form = new ilPropertyFormGUI();
172  $form->setFormAction($this->ctrl->getFormAction($this));
173  $form->setId('tst_question_correction');
174 
175  $form->setTitle($this->language->txt('tst_corrections_qst_form'));
176 
177  $hiddenQid = new ilHiddenInputGUI('qid');
178  $hiddenQid->setValue((string) $questionGUI->object->getId());
179  $form->addItem($hiddenQid);
180 
181  $questionGUI->populateCorrectionsFormProperties($form);
182 
183  $scoring = new ilTestScoring($this->testOBJ, $this->database);
184  $scoring->setQuestionId($questionGUI->object->getId());
185 
186  if ($scoring->getNumManualScorings()) {
187  $form->addCommandButton('confirmManualScoringReset', $this->language->txt('save'));
188  } else {
189  $form->addCommandButton('saveQuestion', $this->language->txt('save'));
190  }
191 
192  return $form;
193  }
194 
195  protected function addHiddenItemsFromArray(ilConfirmationGUI $gui, $array, $curPath = array())
196  {
197  foreach ($array as $name => $value) {
198  if ($name == 'cmd' && !count($curPath)) {
199  continue;
200  }
201 
202  if (count($curPath)) {
203  $name = "[{$name}]";
204  }
205 
206  if (is_array($value)) {
207  $nextPath = array_merge($curPath, array($name));
208  $this->addHiddenItemsFromArray($gui, $value, $nextPath);
209  } else {
210  $postVar = implode('', $curPath) . $name;
211  $gui->addHiddenItem($postVar, $value);
212  }
213  }
214  }
215 
216  protected function confirmManualScoringReset()
217  {
218  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
219 
220  $this->setCorrectionTabsContext($questionGUI, 'question');
221 
222  $scoring = new ilTestScoring($this->testOBJ, $this->database);
223  $scoring->setQuestionId($questionGUI->object->getId());
224 
225  $confirmation = sprintf(
226  $this->language->txt('tst_corrections_manscore_reset_warning'),
227  $scoring->getNumManualScorings(),
228  $questionGUI->object->getTitleForHTMLOutput(),
229  $questionGUI->object->getId()
230  );
231 
232  $gui = new ilConfirmationGUI();
233  $gui->setHeaderText($confirmation);
234  $gui->setFormAction($this->ctrl->getFormAction($this));
235  $gui->setCancel($this->language->txt('cancel'), 'showQuestion');
236  $gui->setConfirm($this->language->txt('confirm'), 'saveQuestion');
237 
238  $this->addHiddenItemsFromArray($gui, $this->testrequest->getParsedBody());
239 
240  $this->main_tpl->setContent($gui->getHTML());
241  }
242 
243  protected function showSolution()
244  {
245  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
246 
247 
248  $pageGUI = new ilAssQuestionPageGUI($questionGUI->object->getId());
249  $pageGUI->setRenderPageContainer(false);
250  $pageGUI->setEditPreview(true);
251  $pageGUI->setEnabledTabs(false);
252 
253  $solutionHTML = $questionGUI->getSolutionOutput(
254  0,
255  null,
256  false,
257  false,
258  true,
259  false,
260  true,
261  false,
262  true
263  );
264 
265  $pageGUI->setQuestionHTML(array($questionGUI->object->getId() => $solutionHTML));
266  $pageGUI->setPresentationTitle($questionGUI->object->getTitleForHTMLOutput());
267 
268  $tpl = new ilTemplate('tpl.tst_corrections_solution_presentation.html', true, true, 'Modules/Test');
269  $tpl->setVariable('SOLUTION_PRESENTATION', $pageGUI->preview());
270 
271  $this->populatePageTitleAndDescription($questionGUI);
272 
273  $this->main_tpl->setContent($tpl->get());
274 
275  $this->main_tpl->setCurrentBlock("ContentStyle");
276  $stylesheet = ilObjStyleSheet::getContentStylePath(0);
277  $this->main_tpl->setVariable("LOCATION_CONTENT_STYLESHEET", $stylesheet);
278  $this->main_tpl->parseCurrentBlock();
279 
280  $this->main_tpl->setCurrentBlock("SyntaxStyle");
281  $stylesheet = ilObjStyleSheet::getSyntaxStylePath();
282  $this->main_tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", $stylesheet);
283  $this->main_tpl->parseCurrentBlock();
284  $this->setCorrectionTabsContext($questionGUI, 'solution');
285  }
286 
290  protected function showAnswerStatistic(?array $participant_results = null)
291  {
292  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
293  $solutions = $participant_results
294  ? $this->getSolutionsByParticipantResults($questionGUI->object, $participant_results)
295  : $this->getSolutions($questionGUI->object);
296 
297  $this->setCorrectionTabsContext($questionGUI, 'answers');
298 
299  $tablesHtml = '';
300 
301  foreach ($questionGUI->getSubQuestionsIndex() as $subQuestionIndex) {
302  $table = $questionGUI->getAnswerFrequencyTableGUI(
303  $this,
304  'showAnswerStatistic',
305  $solutions,
306  $subQuestionIndex
307  );
308 
309  $tablesHtml .= $table->getHTML() . $table->getAdditionalHtml();
310  }
311 
312  $this->populatePageTitleAndDescription($questionGUI);
313  $this->main_tpl->setContent($tablesHtml);
314  }
315 
316  protected function addAnswer()
317  {
318  $form_builder = new ilAddAnswerFormBuilder($this, $this->ui_factory, $this->refinery, $this->language, $this->ctrl);
319 
320  $form = $form_builder->buildAddAnswerForm()
321  ->withRequest($this->request);
322 
323  $data = $form->getData();
324  $question_id = $data['question_id'];
325 
326  if (!$this->checkQuestion($question_id)) {
327  $this->main_tpl->setOnScreenMessage('failure', $this->language->txt('form_input_not_valid'));
328  $this->showAnswerStatistic();
329  return;
330  }
331 
332  $question_gui = $this->getQuestion($question_id);
333 
334  $question_index = $data['question_index'];
335  $answer_value = $data['answer_value'];
336  $points = $data['points'];
337 
338  if (!$points) {
339  $this->main_tpl->setOnScreenMessage('failure', $this->language->txt('err_no_numeric_value'));
340  $this->showAnswerStatistic();
341  return;
342  }
343 
344  if ($question_gui->object->isAddableAnswerOptionValue($question_index, $answer_value)) {
345  $question_gui->object->addAnswerOptionValue($question_index, $answer_value, $points);
346  $question_gui->object->saveToDb();
347  }
348 
349  $scoring = new ilTestScoring($this->testOBJ, $this->database);
350  $scoring->setPreserveManualScores(true);
351  $scoring->setQuestionId($question_id);
352  $results = $scoring->recalculateSolutions();
353 
354  $this->main_tpl->setOnScreenMessage('success', $this->language->txt('saved_successfully'));
356  }
357 
358  protected function confirmQuestionRemoval()
359  {
360  $this->tabs->activateTab(ilTestTabsManager::TAB_ID_CORRECTION);
361 
362  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
363 
364  $confirmation = sprintf(
365  $this->language->txt('tst_corrections_qst_remove_confirmation'),
366  $questionGUI->object->getTitleForHTMLOutput(),
367  $questionGUI->object->getId()
368  );
369 
370  $buttons = array(
371  $this->ui_factory->button()->standard(
372  $this->language->txt('confirm'),
373  $this->ctrl->getLinkTarget($this, 'performQuestionRemoval')
374  ),
375  $this->ui_factory->button()->standard(
376  $this->language->txt('cancel'),
377  $this->ctrl->getLinkTarget($this, 'showQuestionList')
378  )
379  );
380 
381  $this->main_tpl->setContent($this->ui_renderer->render(
382  $this->ui_factory->messageBox()->confirmation($confirmation)->withButtons($buttons)
383  ));
384  }
385 
386  protected function performQuestionRemoval(): void
387  {
388  $questionGUI = $this->getQuestion((int) $this->testrequest->raw('qid'));
389  $scoring = new ilTestScoring($this->testOBJ, $this->database);
390 
391  $participantData = new ilTestParticipantData($this->database, $this->language);
392  $participantData->load($this->testOBJ->getTestId());
393 
394  // remove question solutions
395  $questionGUI->object->removeAllExistingSolutions();
396 
397  // remove test question results
398  $scoring->removeAllQuestionResults($questionGUI->object->getId());
399 
400  // remove question from test and reindex remaining questions
401  $this->testOBJ->removeQuestion($questionGUI->object->getId());
402  $reindexedSequencePositionMap = $this->testOBJ->reindexFixedQuestionOrdering();
403  $this->testOBJ->loadQuestions();
404 
405  // remove questions from all sequences
406  $this->testOBJ->removeQuestionFromSequences(
407  $questionGUI->object->getId(),
408  $participantData->getActiveIds(),
409  $reindexedSequencePositionMap
410  );
411 
412  // update pass and test results
413  $scoring->updatePassAndTestResults($participantData->getActiveIds());
414 
415  // trigger learning progress
416  ilLPStatusWrapper::_refreshStatus($this->testOBJ->getId(), $participantData->getUserIds());
417 
418  // finally delete the question itself
419  $questionGUI->object->delete($questionGUI->object->getId());
420 
421  // check for empty test and set test offline
422  if (!count($this->testOBJ->getTestQuestions())) {
423  $object_properties = $this->testOBJ->getObjectProperties();
424  $object_properties->storePropertyIsOnline(
425  $object_properties->getPropertyIsOnline()->withOffline()
426  );
427  }
428 
429  $this->ctrl->setParameter($this, 'qid', '');
430  $this->ctrl->redirect($this, 'showQuestionList');
431  }
432 
433  protected function setCorrectionTabsContext(assQuestionGUI $questionGUI, $activeTabId)
434  {
435  $this->tabs->clearTargets();
436  $this->tabs->clearSubTabs();
437 
438  $this->help->setScreenIdComponent("tst");
439  $this->help->setScreenId("scoringadjust");
440  $this->help->setSubScreenId($activeTabId);
441 
442 
443  $this->tabs->setBackTarget(
444  $this->language->txt('back'),
445  $this->ctrl->getLinkTarget($this, 'showQuestionList')
446  );
447 
448  $this->tabs->addTab(
449  'question',
450  $this->language->txt('tst_corrections_tab_question'),
451  $this->ctrl->getLinkTarget($this, 'showQuestion')
452  );
453 
454  $this->tabs->addTab(
455  'solution',
456  $this->language->txt('tst_corrections_tab_solution'),
457  $this->ctrl->getLinkTarget($this, 'showSolution')
458  );
459 
460  if ($questionGUI->isAnswerFrequencyStatisticSupported()) {
461  $this->tabs->addTab(
462  'answers',
463  $this->language->txt('tst_corrections_tab_statistics'),
464  $this->ctrl->getLinkTarget($this, 'showAnswerStatistic')
465  );
466  }
467 
468  $this->tabs->activateTab($activeTabId);
469  }
470 
474  protected function populatePageTitleAndDescription(assQuestionGUI $questionGUI)
475  {
476  $this->main_tpl->setTitle($questionGUI->object->getTitleForHTMLOutput());
477  $this->main_tpl->setDescription($questionGUI->outQuestionType());
478  }
479 
484  protected function checkQuestion($qId): bool
485  {
486  if (!$this->testOBJ->isTestQuestion($qId)) {
487  return false;
488  }
489 
490  $questionGUI = $this->getQuestion($qId);
491 
492  if (!$this->supportsAdjustment($questionGUI)) {
493  return false;
494  }
495 
496  if (!$this->allowedInAdjustment($questionGUI)) {
497  return false;
498  }
499 
500  return true;
501  }
502 
507  protected function getQuestion($qId): assQuestionGUI
508  {
509  $question = assQuestion::instantiateQuestionGUI($qId);
510  $question->object->setObjId($this->testOBJ->getId());
511 
512  return $question;
513  }
514 
515  protected function getSolutions(assQuestion $question): array
516  {
517  $solutionRows = array();
518 
519  foreach ($this->testOBJ->getParticipants() as $activeId => $participantData) {
520  $passesSelector = new ilTestPassesSelector($this->database, $this->testOBJ);
521  $passesSelector->setActiveId($activeId);
522  $passesSelector->loadLastFinishedPass();
523 
524  foreach ($passesSelector->getClosedPasses() as $pass) {
525  foreach ($question->getSolutionValues($activeId, $pass) as $row) {
526  $solutionRows[] = $row;
527  }
528  }
529  }
530 
531  return $solutionRows;
532  }
533 
537  protected function getSolutionsByParticipantResults(assQuestion $question, array $participant_results): array
538  {
539  $solutions = [];
540 
541  foreach ($participant_results as $active_id => $result) {
542  foreach ($result->getPasses() as $pass) {
543  foreach ($question->getSolutionValues($active_id, $pass->getPass()) as $row) {
544  $solutions[] = $row;
545  }
546  }
547  }
548 
549  return $solutions;
550  }
551 
555  protected function getQuestions(): array
556  {
557  $questions = array();
558 
559  foreach ($this->testOBJ->getTestQuestions() as $questionData) {
560  $questionGUI = $this->getQuestion($questionData['question_id']);
561 
562  if (!$this->supportsAdjustment($questionGUI)) {
563  continue;
564  }
565 
566  if (!$this->allowedInAdjustment($questionGUI)) {
567  continue;
568  }
569 
570  $questions[] = $questionData;
571  }
572 
573  return $questions;
574  }
575 
583  protected function supportsAdjustment(\assQuestionGUI $question_object): bool
584  {
585  return ($question_object instanceof ilGuiQuestionScoringAdjustable
586  || $question_object instanceof ilGuiAnswerScoringAdjustable)
587  && ($question_object->object instanceof ilObjQuestionScoringAdjustable
588  || $question_object->object instanceof ilObjAnswerScoringAdjustable);
589  }
590 
597  protected function allowedInAdjustment(\assQuestionGUI $question_object): bool
598  {
599  $type_def = [];
600  foreach (explode(',', (new ilSetting('assessment'))->get('assessment_scoring_adjustment', '')) as $type) {
601  $type_def[$type] = ilObjQuestionPool::getQuestionTypeByTypeId($type);
602  }
603 
604  return in_array($question_object->getQuestionType(), $type_def);
605  }
606 }
getSolutionValues($active_id, $pass=null, bool $authorized=true)
Loads solutions of a given user from the database an returns it.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
buildQuestionCorrectionForm(assQuestionGUI $questionGUI)
addHiddenItemsFromArray(ilConfirmationGUI $gui, $array, $curPath=array())
Abstract basic class which is to be extended by the concrete assessment question type classes...
static getQuestionTypeByTypeId($type_id)
Help GUI class.
getTestId()
Gets the database id of the additional test data.
showAnswerStatistic(?array $participant_results=null)
addHiddenItem(string $a_post_var, string $a_value)
setQuestionRemoveRowButtonEnabled(bool $questionRemoveRowButtonEnabled)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static instantiateQuestionGUI(int $a_question_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
populateCorrectionsFormProperties(ilPropertyFormGUI $form)
setCorrectionTabsContext(assQuestionGUI $questionGUI, $activeTabId)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$lng
setRenderPageContainer(bool $a_val)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getSolutions(assQuestion $question)
static _refreshStatus(int $a_obj_id, ?array $a_users=null)
allowedInAdjustment(\assQuestionGUI $question_object)
Returns if the question type is allowed for adjustments in the global test administration.
$results
Basic GUI class for assessment questions.
$txt
Definition: error.php:14
Class ilTestScoring.
static getContentStylePath(int $a_style_id, bool $add_random=true, bool $add_token=true)
get content style path static (to avoid full reading)
supportsAdjustment(\assQuestionGUI $question_object)
Returns if the given question object support scoring adjustment.
populatePageTitleAndDescription(assQuestionGUI $questionGUI)
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...
getSolutionsByParticipantResults(assQuestion $question, array $participant_results)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
showQuestion(ilPropertyFormGUI $form=null)
__construct(protected ilDBInterface $database, protected ilCtrl $ctrl, protected ilAccessHandler $access, protected ilLanguage $language, protected ilTabsGUI $tabs, protected ilHelpGUI $help, protected UIFactory $ui_factory, protected UIRenderer $ui_renderer, protected ilGlobalTemplateInterface $main_tpl, protected RefineryFactory $refinery, protected RequestInterface $request, private InternalRequestService $testrequest, protected ilObjTest $testOBJ, protected QuestionInfoService $questioninfo)
ilTestCorrectionsGUI constructor.
Refinery Factory $refinery