1<?php 2 3/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 4 5/** 6 * Class ilTestCorrectionsGUI 7 * 8 * @author Björn Heyser <info@bjoernheyser.de> 9 * @version $Id$ 10 * 11 * @package Modules/Test 12 */ 13class ilTestCorrectionsGUI 14{ 15 /** 16 * @var \ILIAS\DI\Container 17 */ 18 protected $DIC; 19 20 /** 21 * @var ilObjTest 22 */ 23 protected $testOBJ; 24 25 /** 26 * @var ilTestAccess 27 */ 28 protected $testAccess; 29 30 /** 31 * ilTestCorrectionsGUI constructor. 32 * @param \ILIAS\DI\Container $DIC 33 * @param ilObjTest $testOBJ 34 */ 35 public function __construct(\ILIAS\DI\Container $DIC, ilObjTest $testOBJ) 36 { 37 $this->DIC = $DIC; 38 $this->testOBJ = $testOBJ; 39 40 $this->testAccess = new ilTestAccess($testOBJ->getRefId(), $testOBJ->getTestId()); 41 } 42 43 public function executeCommand() 44 { 45 if (!$this->testAccess->checkCorrectionsAccess()) { 46 ilObjTestGUI::accessViolationRedirect(); 47 } 48 49 if (isset($_GET['eqid']) && (int) $_GET["eqid"] && isset($_GET['eqpl']) && (int) $_GET["eqpl"]) { 50 $this->DIC->ctrl()->setParameter($this, 'qid', (int) $_GET["eqid"]); 51 $this->DIC->ctrl()->redirect($this, 'showQuestion'); 52 } 53 54 if (isset($_GET['removeQid']) && (int) $_GET['removeQid']) { 55 $this->DIC->ctrl()->setParameter($this, 'qid', (int) $_GET['removeQid']); 56 $this->DIC->ctrl()->redirect($this, 'confirmQuestionRemoval'); 57 } 58 59 if ((int) $_GET['qid'] && !$this->checkQuestion((int) $_GET['qid'])) { 60 ilObjTestGUI::accessViolationRedirect(); 61 } 62 63 $this->DIC->ctrl()->saveParameter($this, 'qid'); 64 65 switch ($this->DIC->ctrl()->getNextClass($this)) { 66 default: 67 68 $command = $this->DIC->ctrl()->getCmd('showQuestionList'); 69 $this->{$command}(); 70 } 71 } 72 73 protected function showQuestionList() 74 { 75 $this->DIC->tabs()->activateTab(ilTestTabsManager::TAB_ID_CORRECTION); 76 77 $ui = $this->DIC->ui(); 78 79 if ($this->testOBJ->isFixedTest()) { 80 $table_gui = new ilTestQuestionsTableGUI( 81 $this, 82 'showQuestionList', 83 $this->testOBJ->getRefId() 84 ); 85 86 $table_gui->setQuestionTitleLinksEnabled(true); 87 $table_gui->setQuestionRemoveRowButtonEnabled(true); 88 $table_gui->init(); 89 90 $table_gui->setData($this->getQuestions()); 91 92 $rendered_gui_component = $table_gui->getHTML(); 93 } else { 94 $lng = $this->DIC->language(); 95 $txt = $lng->txt('tst_corrections_incompatible_question_set_type'); 96 97 $infoBox = $ui->factory()->messageBox()->info($txt); 98 99 $rendered_gui_component = $ui->renderer()->render($infoBox); 100 } 101 102 $ui->mainTemplate()->setContent($rendered_gui_component); 103 } 104 105 protected function showQuestion(ilPropertyFormGUI $form = null) 106 { 107 $questionGUI = $this->getQuestion((int) $_GET['qid']); 108 109 $this->setCorrectionTabsContext($questionGUI, 'question'); 110 111 if ($form === null) { 112 $form = $this->buildQuestionCorrectionForm($questionGUI); 113 } 114 115 $this->populatePageTitleAndDescription($questionGUI); 116 $this->DIC->ui()->mainTemplate()->setContent($form->getHTML()); 117 } 118 119 protected function saveQuestion() 120 { 121 $questionGUI = $this->getQuestion((int) $_GET['qid']); 122 123 $form = $this->buildQuestionCorrectionForm($questionGUI); 124 125 $form->setValuesByPost(); 126 127 if (!$form->checkInput()) { 128 $questionGUI->prepareReprintableCorrectionsForm($form); 129 130 $this->showQuestion($form); 131 return; 132 } 133 134 $questionGUI->saveCorrectionsFormProperties($form); 135 $questionGUI->object->setPoints($questionGUI->object->getMaximumPoints()); 136 $questionGUI->object->saveToDb(); 137 138 $scoring = new ilTestScoring($this->testOBJ); 139 $scoring->setPreserveManualScores(false); 140 $scoring->setQuestionId($questionGUI->object->getId()); 141 $scoring->recalculateSolutions(); 142 143 $this->DIC->ctrl()->redirect($this, 'showQuestion'); 144 } 145 146 protected function buildQuestionCorrectionForm(assQuestionGUI $questionGUI) 147 { 148 $form = new ilPropertyFormGUI(); 149 $form->setFormAction($this->DIC->ctrl()->getFormAction($this)); 150 $form->setId('tst_question_correction'); 151 152 $form->setTitle($this->DIC->language()->txt('tst_corrections_qst_form')); 153 154 $hiddenQid = new ilHiddenInputGUI('qid'); 155 $hiddenQid->setValue($questionGUI->object->getId()); 156 $form->addItem($hiddenQid); 157 158 $questionGUI->populateCorrectionsFormProperties($form); 159 160 $scoring = new ilTestScoring($this->testOBJ); 161 $scoring->setQuestionId($questionGUI->object->getId()); 162 163 if ($scoring->getNumManualScorings()) { 164 $form->addCommandButton('confirmManualScoringReset', $this->DIC->language()->txt('save')); 165 } else { 166 $form->addCommandButton('saveQuestion', $this->DIC->language()->txt('save')); 167 } 168 169 return $form; 170 } 171 172 protected function addHiddenItemsFromArray(ilConfirmationGUI $gui, $array, $curPath = array()) 173 { 174 foreach ($array as $name => $value) { 175 if ($name == 'cmd' && !count($curPath)) { 176 continue; 177 } 178 179 if (count($curPath)) { 180 $name = "[{$name}]"; 181 } 182 183 if (is_array($value)) { 184 $nextPath = array_merge($curPath, array($name)); 185 $this->addHiddenItemsFromArray($gui, $value, $nextPath); 186 } else { 187 $postVar = implode('', $curPath) . $name; 188 $gui->addHiddenItem($postVar, $value); 189 } 190 } 191 } 192 193 protected function confirmManualScoringReset() 194 { 195 $questionGUI = $this->getQuestion((int) $_GET['qid']); 196 197 $this->setCorrectionTabsContext($questionGUI, 'question'); 198 199 $scoring = new ilTestScoring($this->testOBJ); 200 $scoring->setQuestionId($questionGUI->object->getId()); 201 202 $confirmation = sprintf( 203 $this->DIC->language()->txt('tst_corrections_manscore_reset_warning'), 204 $scoring->getNumManualScorings(), 205 $questionGUI->object->getTitle(), 206 $questionGUI->object->getId() 207 ); 208 209 $gui = new ilConfirmationGUI(); 210 $gui->setHeaderText($confirmation); 211 $gui->setFormAction($this->DIC->ctrl()->getFormAction($this)); 212 $gui->setCancel($this->DIC->language()->txt('cancel'), 'showQuestion'); 213 $gui->setConfirm($this->DIC->language()->txt('confirm'), 'saveQuestion'); 214 215 $this->addHiddenItemsFromArray($gui, $_POST); 216 217 $this->DIC->ui()->mainTemplate()->setContent($gui->getHTML()); 218 } 219 220 protected function showSolution() 221 { 222 $questionGUI = $this->getQuestion((int) $_GET['qid']); 223 224 $this->setCorrectionTabsContext($questionGUI, 'solution'); 225 226 $pageGUI = new ilAssQuestionPageGUI($questionGUI->object->getId()); 227 $pageGUI->setRenderPageContainer(false); 228 $pageGUI->setEditPreview(true); 229 $pageGUI->setEnabledTabs(false); 230 231 $solutionHTML = $questionGUI->getSolutionOutput( 232 0, 233 null, 234 false, 235 false, 236 true, 237 false, 238 true, 239 false, 240 true 241 ); 242 243 $pageGUI->setQuestionHTML(array($questionGUI->object->getId() => $solutionHTML)); 244 $pageGUI->setPresentationTitle($questionGUI->object->getTitle()); 245 246 $tpl = new ilTemplate('tpl.tst_corrections_solution_presentation.html', true, true, 'Modules/Test'); 247 $tpl->setVariable('SOLUTION_PRESENTATION', $pageGUI->preview()); 248 249 $this->populatePageTitleAndDescription($questionGUI); 250 251 $this->DIC->ui()->mainTemplate()->setContent($tpl->get()); 252 $this->DIC->ui()->mainTemplate()->addCss('Modules/Test/templates/default/ta.css'); 253 254 $this->DIC->ui()->mainTemplate()->setCurrentBlock("ContentStyle"); 255 $stylesheet = ilObjStyleSheet::getContentStylePath(0); 256 $this->DIC->ui()->mainTemplate()->setVariable("LOCATION_CONTENT_STYLESHEET", $stylesheet); 257 $this->DIC->ui()->mainTemplate()->parseCurrentBlock(); 258 259 $this->DIC->ui()->mainTemplate()->setCurrentBlock("SyntaxStyle"); 260 $stylesheet = ilObjStyleSheet::getSyntaxStylePath(); 261 $this->DIC->ui()->mainTemplate()->setVariable("LOCATION_SYNTAX_STYLESHEET", $stylesheet); 262 $this->DIC->ui()->mainTemplate()->parseCurrentBlock(); 263 } 264 265 protected function showAnswerStatistic() 266 { 267 $questionGUI = $this->getQuestion((int) $_GET['qid']); 268 $solutions = $this->getSolutions($questionGUI->object); 269 270 $this->setCorrectionTabsContext($questionGUI, 'answers'); 271 272 $tablesHtml = ''; 273 274 foreach ($questionGUI->getSubQuestionsIndex() as $subQuestionIndex) { 275 $table = $questionGUI->getAnswerFrequencyTableGUI( 276 $this, 277 'showAnswerStatistic', 278 $solutions, 279 $subQuestionIndex 280 ); 281 282 $tablesHtml .= $table->getHTML() . $table->getAdditionalHtml(); 283 } 284 285 $this->populatePageTitleAndDescription($questionGUI); 286 $this->DIC->ui()->mainTemplate()->setContent($tablesHtml); 287 $this->DIC->ui()->mainTemplate()->addCss('Modules/Test/templates/default/ta.css'); 288 } 289 290 protected function addAnswerAsynch() 291 { 292 $response = new stdClass(); 293 294 $form = new ilAddAnswerModalFormGUI(); 295 $form->build(); 296 $form->setValuesByPost(); 297 298 if (!$form->checkInput()) { 299 $uid = md5($form->getInput('answer')); 300 301 $form->setId($uid); 302 $form->setFormAction($this->DIC->ctrl()->getFormAction($this, 'addAnswerAsynch')); 303 304 $alert = $this->DIC->ui()->factory()->messageBox()->failure( 305 $this->DIC->language()->txt('form_input_not_valid') 306 ); 307 308 $bodyTpl = new ilTemplate('tpl.tst_corr_addanswermodal.html', true, true, 'Modules/TestQuestionPool'); 309 $bodyTpl->setVariable('MESSAGE', $this->DIC->ui()->renderer()->render($alert)); 310 $bodyTpl->setVariable('FORM', $form->getHTML()); 311 $bodyTpl->setVariable('BODY_UID', $uid); 312 313 $response->result = false; 314 $response->html = $bodyTpl->get(); 315 316 echo json_encode($response); 317 exit; 318 } 319 320 $qid = (int) $form->getInput('qid'); 321 322 if (!$this->checkQuestion($qid)) { 323 $response->html = ''; 324 $response->result = false; 325 326 echo json_encode($response); 327 exit; 328 } 329 330 $questionGUI = $this->getQuestion($qid); 331 332 $qIndex = (int) $form->getInput('qindex'); 333 $points = (float) $form->getInput('points'); 334 $answerOption = $form->getInput('answer'); 335 336 if ($questionGUI->object->isAddableAnswerOptionValue($qIndex, $answerOption)) { 337 $questionGUI->object->addAnswerOptionValue($qIndex, $answerOption, $points); 338 $questionGUI->object->saveToDb(); 339 } 340 341 $scoring = new ilTestScoring($this->testOBJ); 342 $scoring->setPreserveManualScores(true); 343 $scoring->recalculateSolutions(); 344 345 $response->result = true; 346 347 echo json_encode($response); 348 exit; 349 } 350 351 protected function confirmQuestionRemoval() 352 { 353 $this->DIC->tabs()->activateTab(ilTestTabsManager::TAB_ID_CORRECTION); 354 355 $questionGUI = $this->getQuestion((int) $_GET['qid']); 356 357 $confirmation = sprintf( 358 $this->DIC->language()->txt('tst_corrections_qst_remove_confirmation'), 359 $questionGUI->object->getTitle(), 360 $questionGUI->object->getId() 361 ); 362 363 $buttons = array( 364 $this->DIC->ui()->factory()->button()->standard( 365 $this->DIC->language()->txt('confirm'), 366 $this->DIC->ctrl()->getLinkTarget($this, 'performQuestionRemoval') 367 ), 368 $this->DIC->ui()->factory()->button()->standard( 369 $this->DIC->language()->txt('cancel'), 370 $this->DIC->ctrl()->getLinkTarget($this, 'showQuestionList') 371 ) 372 ); 373 374 $this->DIC->ui()->mainTemplate()->setContent($this->DIC->ui()->renderer()->render( 375 $this->DIC->ui()->factory()->messageBox()->confirmation($confirmation)->withButtons($buttons) 376 )); 377 } 378 379 protected function performQuestionRemoval() 380 { 381 global $DIC; /* @var ILIAS\DI\Container $DIC */ 382 383 $questionGUI = $this->getQuestion((int) $_GET['qid']); 384 $scoring = new ilTestScoring($this->testOBJ); 385 386 $participantData = new ilTestParticipantData($DIC->database(), $DIC->language()); 387 $participantData->load($this->testOBJ->getTestId()); 388 389 // remove question solutions 390 $questionGUI->object->removeAllExistingSolutions(); 391 392 // remove test question results 393 $scoring->removeAllQuestionResults($questionGUI->object->getId()); 394 395 // remove question from test and reindex remaining questions 396 $this->testOBJ->removeQuestion($questionGUI->object->getId()); 397 $reindexedSequencePositionMap = $this->testOBJ->reindexFixedQuestionOrdering(); 398 $this->testOBJ->loadQuestions(); 399 400 // remove questions from all sequences 401 $this->testOBJ->removeQuestionFromSequences( 402 $questionGUI->object->getId(), 403 $participantData->getActiveIds(), 404 $reindexedSequencePositionMap 405 ); 406 407 // update pass and test results 408 $scoring->updatePassAndTestResults($participantData->getActiveIds()); 409 410 // trigger learning progress 411 ilLPStatusWrapper::_refreshStatus($this->testOBJ->getId(), $participantData->getUserIds()); 412 413 // finally delete the question itself 414 $questionGUI->object->delete($questionGUI->object->getId()); 415 416 // check for empty test and set test offline 417 if (!count($this->testOBJ->getTestQuestions())) { 418 $this->testOBJ->setOnline(false); 419 $this->testOBJ->saveToDb(true); 420 } 421 422 $this->DIC->ctrl()->setParameter($this, 'qid', ''); 423 $this->DIC->ctrl()->redirect($this, 'showQuestionList'); 424 } 425 426 protected function setCorrectionTabsContext(assQuestionGUI $questionGUI, $activeTabId) 427 { 428 $this->DIC->tabs()->clearTargets(); 429 $this->DIC->tabs()->clearSubTabs(); 430 431 $this->DIC->tabs()->setBackTarget( 432 $this->DIC->language()->txt('back'), 433 $this->DIC->ctrl()->getLinkTarget($this, 'showQuestionList') 434 ); 435 436 $this->DIC->tabs()->addTab( 437 'question', 438 $this->DIC->language()->txt('tst_corrections_tab_question'), 439 $this->DIC->ctrl()->getLinkTarget($this, 'showQuestion') 440 ); 441 442 $this->DIC->tabs()->addTab( 443 'solution', 444 $this->DIC->language()->txt('tst_corrections_tab_solution'), 445 $this->DIC->ctrl()->getLinkTarget($this, 'showSolution') 446 ); 447 448 if ($questionGUI->isAnswerFreuqencyStatisticSupported()) { 449 $this->DIC->tabs()->addTab( 450 'answers', 451 $this->DIC->language()->txt('tst_corrections_tab_statistics'), 452 $this->DIC->ctrl()->getLinkTarget($this, 'showAnswerStatistic') 453 ); 454 } 455 456 $this->DIC->tabs()->activateTab($activeTabId); 457 } 458 459 /** 460 * @param assQuestionGUI $questionGUI 461 */ 462 protected function populatePageTitleAndDescription(assQuestionGUI $questionGUI) 463 { 464 $this->DIC->ui()->mainTemplate()->setTitle($questionGUI->object->getTitle()); 465 $this->DIC->ui()->mainTemplate()->setDescription($questionGUI->outQuestionType()); 466 } 467 468 /** 469 * @param int $qId 470 * @return bool 471 */ 472 protected function checkQuestion($qId) 473 { 474 if (!$this->testOBJ->isTestQuestion($qId)) { 475 return false; 476 } 477 478 $questionGUI = $this->getQuestion($qId); 479 480 if (!$this->supportsAdjustment($questionGUI)) { 481 return false; 482 } 483 484 if (!$this->allowedInAdjustment($questionGUI)) { 485 return false; 486 } 487 488 return true; 489 } 490 491 /** 492 * @param int $qId 493 * @return assQuestionGUI 494 */ 495 protected function getQuestion($qId) 496 { 497 $question = assQuestion::instantiateQuestionGUI($qId); 498 $question->object->setObjId($this->testOBJ->getId()); 499 500 return $question; 501 } 502 503 protected function getSolutions(assQuestion $question) 504 { 505 $solutionRows = array(); 506 507 foreach ($this->testOBJ->getParticipants() as $activeId => $participantData) { 508 $passesSelector = new ilTestPassesSelector($this->DIC->database(), $this->testOBJ); 509 $passesSelector->setActiveId($activeId); 510 $passesSelector->loadLastFinishedPass(); 511 512 foreach ($passesSelector->getClosedPasses() as $pass) { 513 foreach ($question->getSolutionValues($activeId, $pass) as $row) { 514 $solutionRows[] = $row; 515 } 516 } 517 } 518 519 return $solutionRows; 520 } 521 522 /** 523 * @return array 524 */ 525 protected function getQuestions() : array 526 { 527 $questions = array(); 528 529 foreach ($this->testOBJ->getTestQuestions() as $questionData) { 530 $questionGUI = $this->getQuestion($questionData['question_id']); 531 532 if (!$this->supportsAdjustment($questionGUI)) { 533 continue; 534 } 535 536 if (!$this->allowedInAdjustment($questionGUI)) { 537 continue; 538 } 539 540 $questions[] = $questionData; 541 } 542 543 return $questions; 544 } 545 546 /** 547 * Returns if the given question object support scoring adjustment. 548 * 549 * @param $question_object assQuestionGUI 550 * 551 * @return bool True, if relevant interfaces are implemented to support scoring adjustment. 552 */ 553 protected function supportsAdjustment(\assQuestionGUI $question_object) 554 { 555 return ($question_object instanceof ilGuiQuestionScoringAdjustable 556 || $question_object instanceof ilGuiAnswerScoringAdjustable) 557 && ($question_object->object instanceof ilObjQuestionScoringAdjustable 558 || $question_object->object instanceof ilObjAnswerScoringAdjustable); 559 } 560 561 /** 562 * Returns if the question type is allowed for adjustments in the global test administration. 563 * 564 * @param assQuestionGUI $question_object 565 * @return bool 566 */ 567 protected function allowedInAdjustment(\assQuestionGUI $question_object) 568 { 569 $setting = new ilSetting('assessment'); 570 $types = explode(',', $setting->get('assessment_scoring_adjustment')); 571 require_once './Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php'; 572 $type_def = array(); 573 foreach ($types as $type) { 574 $type_def[$type] = ilObjQuestionPool::getQuestionTypeByTypeId($type); 575 } 576 577 $type = $question_object->getQuestionType(); 578 if (in_array($type, $type_def)) { 579 return true; 580 } 581 return false; 582 } 583} 584