1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once './Modules/Test/classes/class.ilTestPlayerAbstractGUI.php'; 5 6/** 7 * Output class for assessment test execution 8 * 9 * The ilTestOutputGUI class creates the output for the ilObjTestGUI class when learners execute a test. This saves 10 * some heap space because the ilObjTestGUI class will be much smaller then 11 * 12 * @author Helmut Schottmüller <helmut.schottmueller@mac.com> 13 * @author Björn Heyser <bheyser@databay.de> 14 * @author Maximilian Becker <mbecker@databay.de> 15 * 16 * @version $Id$ 17 * 18 * @inGroup ModulesTest 19 */ 20abstract class ilTestOutputGUI extends ilTestPlayerAbstractGUI 21{ 22 /** 23 * @var ilTestQuestionRelatedObjectivesList 24 */ 25 protected $questionRelatedObjectivesList; 26 27 /** 28 * Execute Command 29 */ 30 public function executeCommand() 31 { 32 global $DIC; 33 $ilDB = $DIC['ilDB']; 34 $ilPluginAdmin = $DIC['ilPluginAdmin']; 35 $lng = $DIC['lng']; 36 $ilTabs = $DIC['ilTabs']; 37 38 $this->checkReadAccess(); 39 40 $ilTabs->clearTargets(); 41 42 $cmd = $this->ctrl->getCmd(); 43 $next_class = $this->ctrl->getNextClass($this); 44 45 $this->ctrl->saveParameter($this, "sequence"); 46 $this->ctrl->saveParameter($this, "pmode"); 47 $this->ctrl->saveParameter($this, "active_id"); 48 49 $this->initAssessmentSettings(); 50 51 $DIC->globalScreen()->tool()->context()->current()->addAdditionalData( 52 ilTestPlayerLayoutProvider::TEST_PLAYER_KIOSK_MODE_ENABLED, 53 $this->object->getKioskMode() 54 ); 55 56 $testSessionFactory = new ilTestSessionFactory($this->object); 57 $this->testSession = $testSessionFactory->getSession($_GET['active_id']); 58 59 $this->ensureExistingTestSession($this->testSession); 60 $this->checkTestSessionUser($this->testSession); 61 62 $this->initProcessLocker($this->testSession->getActiveId()); 63 64 $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $ilPluginAdmin, $this->object); 65 $this->testSequence = $testSequenceFactory->getSequenceByTestSession($this->testSession); 66 $this->testSequence->loadFromDb(); 67 $this->testSequence->loadQuestions(); 68 69 require_once 'Modules/Test/classes/class.ilTestQuestionRelatedObjectivesList.php'; 70 $this->questionRelatedObjectivesList = new ilTestQuestionRelatedObjectivesList(); 71 72 include_once 'Services/jQuery/classes/class.iljQueryUtil.php'; 73 iljQueryUtil::initjQuery(); 74 include_once "./Services/YUI/classes/class.ilYuiUtil.php"; 75 ilYuiUtil::initConnectionWithAnimation(); 76 77 $this->handlePasswordProtectionRedirect(); 78 79 $cmd = $this->getCommand($cmd); 80 81 switch ($next_class) { 82 case 'ilassquestionpagegui': 83 84 $this->checkTestExecutable(); 85 86 $questionId = $this->testSequence->getQuestionForSequence($this->getCurrentSequenceElement()); 87 88 require_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPageGUI.php"; 89 $page_gui = new ilAssQuestionPageGUI($questionId); 90 $ret = $this->ctrl->forwardCommand($page_gui); 91 break; 92 93 case 'iltestsubmissionreviewgui': 94 95 $this->checkTestExecutable(); 96 97 require_once './Modules/Test/classes/class.ilTestSubmissionReviewGUI.php'; 98 $gui = new ilTestSubmissionReviewGUI($this, $this->object, $this->testSession); 99 $gui->setObjectiveOrientedContainer($this->getObjectiveOrientedContainer()); 100 $ret = $this->ctrl->forwardCommand($gui); 101 break; 102 103 case 'ilassquestionhintrequestgui': 104 105 $this->checkTestExecutable(); 106 107 $questionGUI = $this->object->createQuestionGUI( 108 "", 109 $this->testSequence->getQuestionForSequence($this->getCurrentSequenceElement()) 110 ); 111 112 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php'; 113 $questionHintTracking = new ilAssQuestionHintTracking( 114 $questionGUI->object->getId(), 115 $this->testSession->getActiveId(), 116 $this->testSession->getPass() 117 ); 118 119 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php'; 120 $gui = new ilAssQuestionHintRequestGUI($this, ilTestPlayerCommands::SHOW_QUESTION, $questionGUI, $questionHintTracking); 121 122// fau: testNav - save the 'answer changed' status for viewing hint requests 123 $this->setAnswerChangedParameter($this->getAnswerChangedParameter()); 124// fau. 125 $ret = $this->ctrl->forwardCommand($gui); 126 127 break; 128 129 case 'iltestsignaturegui': 130 131 $this->checkTestExecutable(); 132 133 require_once './Modules/Test/classes/class.ilTestSignatureGUI.php'; 134 $gui = new ilTestSignatureGUI($this); 135 $ret = $this->ctrl->forwardCommand($gui); 136 break; 137 138 case 'iltestpasswordprotectiongui': 139 140 $this->checkTestExecutable(); 141 142 require_once 'Modules/Test/classes/class.ilTestPasswordProtectionGUI.php'; 143 $gui = new ilTestPasswordProtectionGUI($this->ctrl, $this->tpl, $this->lng, $this, $this->passwordChecker); 144 $ret = $this->ctrl->forwardCommand($gui); 145 break; 146 147 default: 148 149 if (ilTestPlayerCommands::isTestExecutionCommand($cmd)) { 150 $this->checkTestExecutable(); 151 } 152 153 $cmd .= 'Cmd'; 154 $ret = &$this->$cmd(); 155 break; 156 } 157 return $ret; 158 } 159 160 protected function startTestCmd() 161 { 162 global $DIC; 163 $ilUser = $DIC['ilUser']; 164 165 $_SESSION['tst_pass_finish'] = 0; 166 167 // ensure existing test session 168 $this->testSession->setUserId($ilUser->getId()); 169 $this->testSession->setAnonymousId($_SESSION["tst_access_code"][$this->object->getTestId()]); 170 $this->testSession->setObjectiveOrientedContainerId($this->getObjectiveOrientedContainer()->getObjId()); 171 $this->testSession->saveToDb(); 172 173 $active_id = $this->testSession->getActiveId(); 174 $this->ctrl->setParameter($this, "active_id", $active_id); 175 176 $shuffle = $this->object->getShuffleQuestions(); 177 if ($this->object->isRandomTest()) { 178 $this->generateRandomTestPassForActiveUser(); 179 180 $this->object->loadQuestions(); 181 $shuffle = false; // shuffle is already done during the creation of the random questions 182 } 183 184 assQuestion::_updateTestPassResults( 185 $active_id, 186 $this->testSession->getPass(), 187 $this->object->areObligationsEnabled(), 188 null, 189 $this->object->id 190 ); 191 192 // ensure existing test sequence 193 if (!$this->testSequence->hasSequence()) { 194 $this->testSequence->createNewSequence($this->object->getQuestionCount(), $shuffle); 195 $this->testSequence->saveToDb(); 196 } 197 198 $this->testSequence->loadFromDb(); 199 $this->testSequence->loadQuestions(); 200 201 if ($this->testSession->isObjectiveOriented()) { 202 require_once 'Modules/Course/classes/Objectives/class.ilLOTestQuestionAdapter.php'; 203 $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->testSession); 204 205 $objectivesAdapter->notifyTestStart($this->testSession, $this->object->getId()); 206 $objectivesAdapter->prepareTestPass($this->testSession, $this->testSequence); 207 208 $objectivesAdapter->buildQuestionRelatedObjectiveList( 209 $this->testSequence, 210 $this->questionRelatedObjectivesList 211 ); 212 213 if ($this->testSequence->hasOptionalQuestions()) { 214 $this->adoptUserSolutionsFromPreviousPass(); 215 216 $this->testSequence->reorderOptionalQuestionsToSequenceEnd(); 217 $this->testSequence->saveToDb(); 218 } 219 } 220 221 $active_time_id = $this->object->startWorkingTime( 222 $this->testSession->getActiveId(), 223 $this->testSession->getPass() 224 ); 225 $_SESSION["active_time_id"] = $active_time_id; 226 227 $this->updateLearningProgressOnTestStart(); 228 229 $sequenceElement = $this->testSequence->getFirstSequence(); 230 231 $this->ctrl->setParameter($this, 'sequence', $sequenceElement); 232 $this->ctrl->setParameter($this, 'pmode', ''); 233 234 if ($this->object->getListOfQuestionsStart()) { 235 $this->ctrl->redirect($this, ilTestPlayerCommands::QUESTION_SUMMARY); 236 } 237 238 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 239 } 240 241 protected function updateLearningProgressOnTestStart() 242 { 243 global $DIC; 244 $ilUser = $DIC['ilUser']; 245 246 require_once('./Modules/Test/classes/class.ilObjTestAccess.php'); 247 require_once('./Services/Tracking/classes/class.ilLPStatusWrapper.php'); 248 ilLPStatusWrapper::_updateStatus($this->object->getId(), $ilUser->getId()); 249 } 250 251 private function isValidSequenceElement($sequenceElement) 252 { 253 if ($sequenceElement === false) { 254 return false; 255 } 256 257 if ($sequenceElement < 1) { 258 return false; 259 } 260 261 if (!$this->testSequence->getPositionOfSequence($sequenceElement)) { 262 return false; 263 } 264 265 return true; 266 } 267 268 protected function showQuestionCmd() 269 { 270 $_SESSION['tst_pass_finish'] = 0; 271 272 $_SESSION["active_time_id"] = $this->object->startWorkingTime( 273 $this->testSession->getActiveId(), 274 $this->testSession->getPass() 275 ); 276 277 $sequenceElement = $this->getCurrentSequenceElement(); 278 279 if (!$this->isValidSequenceElement($sequenceElement)) { 280 $sequenceElement = $this->testSequence->getFirstSequence(); 281 } 282 283 $this->testSession->setLastSequence($sequenceElement); 284 $this->testSession->saveToDb(); 285 286 287 $questionId = $this->testSequence->getQuestionForSequence($sequenceElement); 288 289 if (!(int) $questionId && $this->testSession->isObjectiveOriented()) { 290 $this->handleTearsAndAngerNoObjectiveOrientedQuestion(); 291 } 292 293 if (!$this->testSequence->isQuestionPresented($questionId)) { 294 $this->testSequence->setQuestionPresented($questionId); 295 $this->testSequence->saveToDb(); 296 } 297 298 $isQuestionWorkedThrough = assQuestion::_isWorkedThrough( 299 $this->testSession->getActiveId(), 300 $questionId, 301 $this->testSession->getPass() 302 ); 303 304 // fau: testNav - always use edit mode, except for fixed answer 305 if ($this->isParticipantsAnswerFixed($questionId)) { 306 $presentationMode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW; 307 $instantResponse = true; 308 } else { 309 $presentationMode = ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT; 310 $instantResponse = $this->getInstantResponseParameter(); 311 } 312 // fau. 313 314 $questionGui = $this->getQuestionGuiInstance($questionId); 315 316 if (!($questionGui instanceof assQuestionGUI)) { 317 $this->handleTearsAndAngerQuestionIsNull($questionId, $sequenceElement); 318 } 319 320 $questionGui->setSequenceNumber($this->testSequence->getPositionOfSequence($sequenceElement)); 321 $questionGui->setQuestionCount($this->testSequence->getUserQuestionCount()); 322 323 require_once 'Modules/Test/classes/class.ilTestQuestionHeaderBlockBuilder.php'; 324 $headerBlockBuilder = new ilTestQuestionHeaderBlockBuilder($this->lng); 325 $headerBlockBuilder->setHeaderMode($this->object->getTitleOutput()); 326 $headerBlockBuilder->setQuestionTitle($questionGui->object->getTitle()); 327 $headerBlockBuilder->setQuestionPoints($questionGui->object->getPoints()); 328 $headerBlockBuilder->setQuestionPosition($this->testSequence->getPositionOfSequence($sequenceElement)); 329 $headerBlockBuilder->setQuestionCount($this->testSequence->getUserQuestionCount()); 330 $headerBlockBuilder->setQuestionPostponed($this->testSequence->isPostponedQuestion($questionId)); 331 $headerBlockBuilder->setQuestionObligatory( 332 $this->object->areObligationsEnabled() && ilObjTest::isQuestionObligatory($questionGui->object->getId()) 333 ); 334 if ($this->testSession->isObjectiveOriented()) { 335 require_once 'Modules/Course/classes/Objectives/class.ilLOTestQuestionAdapter.php'; 336 $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->testSession); 337 $objectivesAdapter->buildQuestionRelatedObjectiveList($this->testSequence, $this->questionRelatedObjectivesList); 338 $this->questionRelatedObjectivesList->loadObjectivesTitles(); 339 340 $objectivesString = $this->questionRelatedObjectivesList->getQuestionRelatedObjectiveTitles($questionId); 341 $headerBlockBuilder->setQuestionRelatedObjectives($objectivesString); 342 } 343 $questionGui->setQuestionHeaderBlockBuilder($headerBlockBuilder); 344 345 $this->prepareTestPage($presentationMode, $sequenceElement, $questionId); 346 347 $navigationToolbarGUI = $this->getTestNavigationToolbarGUI(); 348 $navigationToolbarGUI->setFinishTestButtonEnabled(true); 349 350 $isNextPrimary = $this->handlePrimaryButton($navigationToolbarGUI, $questionId); 351 352 $this->ctrl->setParameter($this, 'sequence', $sequenceElement); 353 $this->ctrl->setParameter($this, 'pmode', $presentationMode); 354 $formAction = $this->ctrl->getFormAction($this, ilTestPlayerCommands::SUBMIT_INTERMEDIATE_SOLUTION); 355 356 switch ($presentationMode) { 357 case ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT: 358 359// fau: testNav - enable navigation toolbar in edit mode 360 $navigationToolbarGUI->setDisabledStateEnabled(false); 361// fau. 362 $this->showQuestionEditable($questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse); 363 364 break; 365 366 case ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW: 367 368 if ($this->testSequence->isQuestionOptional($questionGui->object->getId())) { 369 $this->populateQuestionOptionalMessage(); 370 } 371 372 $this->showQuestionViewable($questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse); 373 374 break; 375 376 default: 377 378 require_once 'Modules/Test/exceptions/class.ilTestException.php'; 379 throw new ilTestException('no presentation mode given'); 380 } 381 382 $navigationToolbarGUI->build(); 383 $this->populateTestNavigationToolbar($navigationToolbarGUI); 384 385 // fau: testNav - enable the question navigation in edit mode 386 $this->populateQuestionNavigation($sequenceElement, false, $isNextPrimary); 387 // fau. 388 389 if ($instantResponse) { 390 // fau: testNav - always use authorized solution for instant feedback 391 $this->populateInstantResponseBlocks( 392 $questionGui, 393 true 394 ); 395 // fau. 396 } 397 398 // fau: testNav - add feedback modal 399 if ($this->isForcedFeedbackNavUrlRegistered()) { 400 $this->populateInstantResponseModal($questionGui, $this->getRegisteredForcedFeedbackNavUrl()); 401 $this->unregisterForcedFeedbackNavUrl(); 402 } 403 // fau. 404 } 405 406 protected function editSolutionCmd() 407 { 408 $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_EDIT); 409 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 410 } 411 412 protected function submitSolutionAndNextCmd() 413 { 414 if ($this->object->isForceInstantFeedbackEnabled()) { 415 return $this->submitSolutionCmd(); 416 } 417 418 if ($this->saveQuestionSolution(true, false)) { 419 $questionId = $this->testSequence->getQuestionForSequence( 420 $this->getCurrentSequenceElement() 421 ); 422 423 $this->removeIntermediateSolution(); 424 425 $nextSequenceElement = $this->testSequence->getNextSequence($this->getCurrentSequenceElement()); 426 427 if (!$this->isValidSequenceElement($nextSequenceElement)) { 428 $nextSequenceElement = $this->testSequence->getFirstSequence(); 429 } 430 431 $this->testSession->setLastSequence($nextSequenceElement); 432 $this->testSession->saveToDb(); 433 434 $this->ctrl->setParameter($this, 'sequence', $nextSequenceElement); 435 $this->ctrl->setParameter($this, 'pmode', ''); 436 } 437 438 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 439 } 440 441 protected function submitSolutionCmd() 442 { 443 if ($this->saveQuestionSolution(true, false)) { 444 $questionId = $this->testSequence->getQuestionForSequence( 445 $this->getCurrentSequenceElement() 446 ); 447 448 $this->removeIntermediateSolution(); 449 450 if ($this->object->isForceInstantFeedbackEnabled()) { 451 $this->ctrl->setParameter($this, 'instresp', 1); 452 453 $this->testSequence->setQuestionChecked($questionId); 454 $this->testSequence->saveToDb(); 455 } 456 457 if ($this->getNextCommandParameter()) { 458 if ($this->getNextSequenceParameter()) { 459 $this->ctrl->setParameter($this, 'sequence', $this->getNextSequenceParameter()); 460 $this->ctrl->setParameter($this, 'pmode', ''); 461 } 462 463 $this->ctrl->redirect($this, $this->getNextCommandParameter()); 464 } 465 466 $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW); 467 } else { 468 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 469 } 470 471 // fau: testNav - remember to prevent the navigation confirmation 472 $this->saveNavigationPreventConfirmation(); 473 // fau. 474 475 // fau: testNav - handle navigation after saving 476 if ($this->getNavigationUrlParameter()) { 477 ilUtil::redirect($this->getNavigationUrlParameter()); 478 } else { 479 $this->ctrl->saveParameter($this, 'sequence'); 480 } 481 // fau. 482 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 483 } 484 485 protected function discardSolutionCmd() 486 { 487 $currentSequenceElement = $this->getCurrentSequenceElement(); 488 489 $currentQuestionOBJ = $this->getQuestionInstance( 490 $this->testSequence->getQuestionForSequence($currentSequenceElement) 491 ); 492 493 $currentQuestionOBJ->resetUsersAnswer( 494 $this->testSession->getActiveId(), 495 $this->testSession->getPass() 496 ); 497 498 $this->ctrl->saveParameter($this, 'sequence'); 499 500 $this->ctrl->setParameter($this, 'pmode', ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW); 501 502 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 503 } 504 505 protected function skipQuestionCmd() 506 { 507 $curSequenceElement = $this->getCurrentSequenceElement(); 508 $nextSequenceElement = $this->testSequence->getNextSequence($curSequenceElement); 509 510 if (!$this->isValidSequenceElement($nextSequenceElement)) { 511 $nextSequenceElement = $this->testSequence->getFirstSequence(); 512 } 513 514 if ($this->object->isPostponingEnabled()) { 515 $this->testSequence->postponeSequence($curSequenceElement); 516 $this->testSequence->saveToDb(); 517 } 518 519 $this->ctrl->setParameter($this, 'sequence', $nextSequenceElement); 520 $this->ctrl->setParameter($this, 'pmode', ''); 521 522 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 523 } 524 525 protected function handleQuestionPostponing($sequenceElement) 526 { 527 $questionId = $this->testSequence->getQuestionForSequence($sequenceElement); 528 529 $isQuestionWorkedThrough = assQuestion::_isWorkedThrough( 530 $this->testSession->getActiveId(), 531 $questionId, 532 $this->testSession->getPass() 533 ); 534 535 if (!$isQuestionWorkedThrough) { 536 $this->testSequence->postponeQuestion($questionId); 537 $this->testSequence->saveToDb(); 538 } 539 } 540 541 protected function nextQuestionCmd() 542 { 543 $lastSequenceElement = $this->getCurrentSequenceElement(); 544 $nextSequenceElement = $this->testSequence->getNextSequence($lastSequenceElement); 545 546 if ($this->object->isPostponingEnabled()) { 547 $this->handleQuestionPostponing($lastSequenceElement); 548 } 549 550 if (!$this->isValidSequenceElement($nextSequenceElement)) { 551 $nextSequenceElement = $this->testSequence->getFirstSequence(); 552 } 553 554 $this->ctrl->setParameter($this, 'sequence', $nextSequenceElement); 555 $this->ctrl->setParameter($this, 'pmode', ''); 556 557 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 558 } 559 560 protected function previousQuestionCmd() 561 { 562 $sequenceElement = $this->testSequence->getPreviousSequence( 563 $this->getCurrentSequenceElement() 564 ); 565 566 if (!$this->isValidSequenceElement($sequenceElement)) { 567 $sequenceElement = $this->testSequence->getLastSequence(); 568 } 569 570 $this->ctrl->setParameter($this, 'sequence', $sequenceElement); 571 $this->ctrl->setParameter($this, 'pmode', ''); 572 573 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 574 } 575 576 protected function isFirstQuestionInSequence($sequenceElement) 577 { 578 return $sequenceElement == $this->testSequence->getFirstSequence(); 579 } 580 581 protected function isLastQuestionInSequence($sequenceElement) 582 { 583 return $sequenceElement == $this->testSequence->getLastSequence(); 584 } 585 586 /** 587 * Returns TRUE if the answers of the current user could be saved 588 * 589 * @return boolean TRUE if the answers could be saved, FALSE otherwise 590 */ 591 protected function canSaveResult() 592 { 593 return !$this->object->endingTimeReached() && !$this->isMaxProcessingTimeReached() && !$this->isNrOfTriesReached(); 594 } 595 596 /** 597 * @return integer 598 */ 599 protected function getCurrentQuestionId() 600 { 601 return $this->testSequence->getQuestionForSequence($_GET["sequence"]); 602 } 603 604 /** 605 * saves the user input of a question 606 */ 607 public function saveQuestionSolution($authorized = true, $force = false) 608 { 609 $this->updateWorkingTime(); 610 $this->saveResult = false; 611 if (!$force) { 612 $formtimestamp = $_POST["formtimestamp"]; 613 if (strlen($formtimestamp) == 0) { 614 $formtimestamp = $_GET["formtimestamp"]; 615 } 616 if ($formtimestamp != $_SESSION["formtimestamp"]) { 617 $_SESSION["formtimestamp"] = $formtimestamp; 618 } else { 619 return false; 620 } 621 } 622 // save question solution 623 if ($this->canSaveResult() || $force) { 624 // but only if the ending time is not reached 625 $q_id = $this->testSequence->getQuestionForSequence($_GET["sequence"]); 626 627 if ($this->isParticipantsAnswerFixed($q_id)) { 628 // should only be reached by firebugging the disabled form in ui 629 throw new ilTestException('not allowed request'); 630 } 631 632 if (is_numeric($q_id) && (int) $q_id) { 633 $questionOBJ = $this->getQuestionInstance($q_id); 634 $pass = null; 635 $active_id = $this->testSession->getActiveId(); 636 if ($this->object->isRandomTest()) { 637 $pass = ilObjTest::_getPass($active_id); 638 } 639 $this->saveResult = $questionOBJ->persistWorkingState( 640 $active_id, 641 $pass, 642 $this->object->areObligationsEnabled(), 643 $authorized 644 ); 645 646 if ($authorized && $this->testSession->isObjectiveOriented()) { 647 require_once 'Modules/Course/classes/Objectives/class.ilLOTestQuestionAdapter.php'; 648 $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->testSession); 649 $objectivesAdapter->updateQuestionResult($this->testSession, $questionOBJ); 650 } 651 652 if ($authorized && $this->object->isSkillServiceToBeConsidered()) { 653 $this->handleSkillTriggering($this->testSession); 654 } 655 } 656 } 657 658 if ($this->saveResult == false || (!$questionOBJ->validateSolutionSubmit() && $questionOBJ->savePartial()) ) { 659 $this->ctrl->setParameter($this, "save_error", "1"); 660 $_SESSION["previouspost"] = $_POST; 661 } 662 663 return $this->saveResult; 664 } 665 666 protected function showInstantResponseCmd() 667 { 668 $questionId = $this->testSequence->getQuestionForSequence( 669 $this->getCurrentSequenceElement() 670 ); 671 672 if ($this->getAnswerChangedParameter() && !$this->isParticipantsAnswerFixed($questionId)) { 673 if ($this->saveQuestionSolution(true)) { 674 $this->removeIntermediateSolution(); 675 $this->setAnswerChangedParameter(false); 676 } else { 677 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 678 } 679 $this->testSequence->setQuestionChecked($questionId); 680 $this->testSequence->saveToDb(); 681 } else if ($this->object->isForceInstantFeedbackEnabled()) { 682 $this->testSequence->setQuestionChecked($questionId); 683 $this->testSequence->saveToDb(); 684 } 685 686 $this->ctrl->setParameter($this, 'instresp', 1); 687 688 // fau: testNav - handle navigation after feedback 689 if ($this->getNavigationUrlParameter()) { 690 $this->saveNavigationPreventConfirmation(); 691 $this->registerForcedFeedbackNavUrl($this->getNavigationUrlParameter()); 692 } 693 // fau. 694 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 695 } 696 697 protected function handleQuestionActionCmd() 698 { 699 $questionId = $this->testSequence->getQuestionForSequence( 700 $this->getCurrentSequenceElement() 701 ); 702 703 if (!$this->isParticipantsAnswerFixed($questionId)) { 704 $this->updateWorkingTime(); 705 $this->saveQuestionSolution(false); 706 // fau: testNav - add changed status of the question 707 $this->setAnswerChangedParameter(true); 708 // fau. 709 } 710 711 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 712 } 713 714 protected function performTearsAndAngerBrokenConfessionChecks() 715 { 716 if ($this->testSession->getActiveId() > 0) { 717 if ($this->testSequence->hasRandomQuestionsForPass($this->testSession->getActiveId(), $this->testSession->getPass()) > 0) { 718 // Something went wrong. Maybe the user pressed the start button twice 719 // Questions already exist so there is no need to create new questions 720 721 global $DIC; 722 $ilLog = $DIC['ilLog']; 723 $ilUser = $DIC['ilUser']; 724 725 $ilLog->write( 726 __METHOD__ . ' Random Questions allready exists for user ' . 727 $ilUser->getId() . ' in test ' . $this->object->getTestId() 728 ); 729 730 return true; 731 } 732 } else { 733 // This may not happen! If it happens, raise a fatal error... 734 735 global $DIC; 736 $ilLog = $DIC['ilLog']; 737 $ilUser = $DIC['ilUser']; 738 739 $ilLog->write(__METHOD__ . ' ' . sprintf( 740 $this->lng->txt("error_random_question_generation"), 741 $ilUser->getId(), 742 $this->object->getTestId() 743 )); 744 745 return true; 746 }; 747 748 return false; 749 } 750 751 protected function generateRandomTestPassForActiveUser() 752 { 753 global $DIC; 754 $tree = $DIC['tree']; 755 $ilDB = $DIC['ilDB']; 756 $ilPluginAdmin = $DIC['ilPluginAdmin']; 757 758 require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetConfig.php'; 759 $questionSetConfig = new ilTestRandomQuestionSetConfig($tree, $ilDB, $ilPluginAdmin, $this->object); 760 $questionSetConfig->loadFromDb(); 761 762 require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php'; 763 $sourcePoolDefinitionFactory = new ilTestRandomQuestionSetSourcePoolDefinitionFactory($ilDB, $this->object); 764 765 require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionList.php'; 766 $sourcePoolDefinitionList = new ilTestRandomQuestionSetSourcePoolDefinitionList($ilDB, $this->object, $sourcePoolDefinitionFactory); 767 $sourcePoolDefinitionList->loadDefinitions(); 768 769 $this->processLocker->executeRandomPassBuildOperation(function () use ($ilDB, $ilPluginAdmin, $questionSetConfig, $sourcePoolDefinitionList) { 770 if (!$this->performTearsAndAngerBrokenConfessionChecks()) { 771 require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetStagingPoolQuestionList.php'; 772 $stagingPoolQuestionList = new ilTestRandomQuestionSetStagingPoolQuestionList($ilDB, $ilPluginAdmin); 773 774 require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetBuilder.php'; 775 $questionSetBuilder = ilTestRandomQuestionSetBuilder::getInstance($ilDB, $this->object, $questionSetConfig, $sourcePoolDefinitionList, $stagingPoolQuestionList); 776 777 $questionSetBuilder->performBuild($this->testSession); 778 } 779 }, $sourcePoolDefinitionList->hasTaxonomyFilters()); 780 } 781 782 /** 783 * Resume a test at the last position 784 */ 785 protected function resumePlayerCmd() 786 { 787 $this->handleUserSettings(); 788 789 $active_id = $this->testSession->getActiveId(); 790 $this->ctrl->setParameter($this, "active_id", $active_id); 791 792 $active_time_id = $this->object->startWorkingTime($active_id, $this->testSession->getPass()); 793 $_SESSION["active_time_id"] = $active_time_id; 794 $_SESSION['tst_pass_finish'] = 0; 795 796 if ($this->object->isRandomTest()) { 797 if (!$this->testSequence->hasRandomQuestionsForPass($active_id, $this->testSession->getPass())) { 798 // create a new set of random questions 799 $this->generateRandomTestPassForActiveUser(); 800 } 801 } 802 803 $shuffle = $this->object->getShuffleQuestions(); 804 if ($this->object->isRandomTest()) { 805 $shuffle = false; 806 } 807 808 assQuestion::_updateTestPassResults( 809 $active_id, 810 $this->testSession->getPass(), 811 $this->object->areObligationsEnabled(), 812 null, 813 $this->object->id 814 ); 815 816 // ensure existing test sequence 817 if (!$this->testSequence->hasSequence()) { 818 $this->testSequence->createNewSequence($this->object->getQuestionCount(), $shuffle); 819 $this->testSequence->saveToDb(); 820 } 821 822 if ($this->object->getListOfQuestionsStart()) { 823 $this->ctrl->redirect($this, ilTestPlayerCommands::QUESTION_SUMMARY); 824 } 825 826 $this->ctrl->setParameter($this, 'sequence', $this->testSession->getLastSequence()); 827 $this->ctrl->setParameter($this, 'pmode', ''); 828 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 829 } 830 831 protected function isShowingPostponeStatusReguired($questionId) 832 { 833 return $this->testSequence->isPostponedQuestion($questionId); 834 } 835 836 protected function adoptUserSolutionsFromPreviousPass() 837 { 838 global $DIC; 839 $ilDB = $DIC['ilDB']; 840 $ilUser = $DIC['ilUser']; 841 842 $assSettings = new ilSetting('assessment'); 843 844 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php"); 845 $isAssessmentLogEnabled = ilObjAssessmentFolder::_enabledAssessmentLogging(); 846 847 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionUserSolutionAdopter.php'; 848 $userSolutionAdopter = new ilAssQuestionUserSolutionAdopter($ilDB, $assSettings, $isAssessmentLogEnabled); 849 850 $userSolutionAdopter->setUserId($ilUser->getId()); 851 $userSolutionAdopter->setActiveId($this->testSession->getActiveId()); 852 $userSolutionAdopter->setTargetPass($this->testSequence->getPass()); 853 $userSolutionAdopter->setQuestionIds($this->testSequence->getOptionalQuestions()); 854 855 $userSolutionAdopter->perform(); 856 } 857 858 abstract protected function populateQuestionOptionalMessage(); 859 860 protected function isOptionalQuestionAnsweringConfirmationRequired($sequenceKey) 861 { 862 if ($this->testSequence->isAnsweringOptionalQuestionsConfirmed()) { 863 return false; 864 } 865 866 $questionId = $this->testSequence->getQuestionForSequence($sequenceKey); 867 868 if (!$this->testSequence->isQuestionOptional($questionId)) { 869 return false; 870 } 871 872 return true; 873 } 874 875 protected function isQuestionSummaryFinishTestButtonRequired() 876 { 877 return true; 878 } 879 880 protected function handleTearsAndAngerNoObjectiveOrientedQuestion() 881 { 882 ilUtil::sendFailure(sprintf($this->lng->txt('tst_objective_oriented_test_pass_without_questions'), $this->object->getTitle()), true); 883 884 $this->backToInfoScreenCmd(); 885 } 886 887 /** 888 * @param ilTestNavigationToolbarGUI $navigationToolbarGUI 889 */ 890 protected function handlePrimaryButton(ilTestNavigationToolbarGUI $navigationToolbarGUI, $currentQuestionId) 891 { 892 $isNextPrimary = true; 893 894 if ($this->object->isForceInstantFeedbackEnabled()) { 895 $isNextPrimary = false; 896 } 897 898 $questionsMissingResult = assQuestion::getQuestionsMissingResultRecord( 899 $this->testSession->getActiveId(), 900 $this->testSession->getPass(), 901 $this->testSequence->getOrderedSequenceQuestions() 902 ); 903 904 if (!count($questionsMissingResult)) { 905 $navigationToolbarGUI->setFinishTestButtonPrimary(true); 906 $isNextPrimary = false; 907 } elseif (count($questionsMissingResult) == 1) { 908 $lastOpenQuestion = current($questionsMissingResult); 909 910 if ($currentQuestionId == $lastOpenQuestion) { 911 $navigationToolbarGUI->setFinishTestButtonPrimary(true); 912 $isNextPrimary = false; 913 } 914 } 915 916 return $isNextPrimary; 917 } 918} 919