1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once './Modules/Test/classes/inc.AssessmentConstants.php'; 5require_once './Modules/Test/classes/class.ilTestPlayerCommands.php'; 6require_once './Modules/Test/classes/class.ilTestServiceGUI.php'; 7require_once './Modules/TestQuestionPool/classes/class.assQuestion.php'; 8require_once './Services/UIComponent/Button/classes/class.ilSubmitButton.php'; 9require_once 'Modules/Test/classes/class.ilTestPlayerNavButton.php'; 10 11/** 12 * Output class for assessment test execution 13 * 14 * The ilTestOutputGUI class creates the output for the ilObjTestGUI class when learners execute a test. This saves 15 * some heap space because the ilObjTestGUI class will be much smaller then 16 * 17 * @author Björn Heyser <bheyser@databay.de> 18 * @author Maximilian Becker <mbecker@databay.de> 19 * 20 * @version $Id$ 21 * 22 * @inGroup ModulesTest 23 * 24 */ 25abstract class ilTestPlayerAbstractGUI extends ilTestServiceGUI 26{ 27 const PRESENTATION_MODE_VIEW = 'view'; 28 const PRESENTATION_MODE_EDIT = 'edit'; 29 30 const FIXED_SHUFFLER_SEED_MIN_LENGTH = 8; 31 32 public $ref_id; 33 public $saveResult; 34 public $sequence; 35 public $cmdCtrl; 36 public $maxProcessingTimeReached; 37 public $endingTimeReached; 38 39 /** 40 * @var ilTestPasswordChecker 41 */ 42 protected $passwordChecker; 43 44 /** 45 * @var ilTestProcessLocker 46 */ 47 protected $processLocker; 48 49 /** 50 * @var ilTestSession 51 */ 52 protected $testSession; 53 54 /** 55 * @var ilSetting 56 */ 57 protected $assSettings; 58 59 /** 60 * @var ilTestSequence|ilTestSequenceDynamicQuestionSet 61 */ 62 protected $testSequence = null; 63 64 /** 65 * ilTestOutputGUI constructor 66 * 67 * @param ilObjTest $a_object 68 */ 69 public function __construct($a_object) 70 { 71 parent::__construct($a_object); 72 $this->ref_id = $_GET["ref_id"]; 73 74 global $DIC; 75 $rbacsystem = $DIC['rbacsystem']; 76 $ilUser = $DIC['ilUser']; 77 $lng = $DIC['lng']; 78 require_once 'Modules/Test/classes/class.ilTestPasswordChecker.php'; 79 $this->passwordChecker = new ilTestPasswordChecker($rbacsystem, $ilUser, $this->object, $lng); 80 81 $this->processLocker = null; 82 $this->testSession = null; 83 $this->assSettings = null; 84 } 85 86 protected function checkReadAccess() 87 { 88 global $DIC; 89 $rbacsystem = $DIC['rbacsystem']; 90 91 if (!$rbacsystem->checkAccess("read", $this->object->getRefId())) { 92 // only with read access it is possible to run the test 93 $this->ilias->raiseError($this->lng->txt("cannot_execute_test"), $this->ilias->error_obj->MESSAGE); 94 } 95 } 96 97 protected function checkTestExecutable() 98 { 99 $executable = $this->object->isExecutable($this->testSession, $this->testSession->getUserId()); 100 101 if (!$executable['executable']) { 102 ilUtil::sendInfo($executable['errormessage'], true); 103 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen"); 104 } 105 } 106 107 protected function checkTestSessionUser(ilTestSession $testSession) 108 { 109 global $DIC; /* @var ILIAS\DI\Container $DIC */ 110 111 if ($testSession->getUserId() != $DIC->user()->getId()) { 112 throw new ilTestException('active id given does not relate to current user!'); 113 } 114 } 115 116 protected function ensureExistingTestSession(ilTestSession $testSession) 117 { 118 if ($testSession->getActiveId()) { 119 return; 120 } 121 122 global $DIC; 123 $ilUser = $DIC['ilUser']; 124 125 $testSession->setUserId($ilUser->getId()); 126 127 if ($testSession->isAnonymousUser()) { 128 if (!$testSession->doesAccessCodeInSessionExists()) { 129 return; 130 } 131 132 $testSession->setAnonymousId($testSession->getAccessCodeFromSession()); 133 } 134 135 $testSession->saveToDb(); 136 } 137 138 protected function initProcessLocker($activeId) 139 { 140 global $DIC; 141 $ilDB = $DIC['ilDB']; 142 143 require_once 'Modules/Test/classes/class.ilTestProcessLockerFactory.php'; 144 $processLockerFactory = new ilTestProcessLockerFactory($this->assSettings, $ilDB); 145 146 $processLockerFactory->setActiveId($activeId); 147 148 $this->processLocker = $processLockerFactory->getLocker(); 149 } 150 151 /** 152 * Save tags for tagging gui 153 * 154 * Needed this function here because the test info page 155 * uses another class to send its form results 156 */ 157 public function saveTagsCmd() 158 { 159 include_once("./Services/Tagging/classes/class.ilTaggingGUI.php"); 160 $tagging_gui = new ilTaggingGUI(); 161 $tagging_gui->setObject($this->object->getId(), $this->object->getType()); 162 $tagging_gui->saveInput(); 163 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen"); 164 } 165 166 /** 167 * updates working time and stores state saveresult to see if question has to be stored or not 168 */ 169 public function updateWorkingTime() 170 { 171 if ($_SESSION["active_time_id"]) { 172 $this->object->updateWorkingTime($_SESSION["active_time_id"]); 173 } 174 175 $_SESSION["active_time_id"] = $this->object->startWorkingTime( 176 $this->testSession->getActiveId(), 177 $this->testSession->getPass() 178 ); 179 } 180 181 // fau: testNav - new function removeIntermediateSolution() 182 /** 183 * remove an auto-saved solution of the current question 184 * @return mixed number of rows or db error 185 */ 186 public function removeIntermediateSolution() 187 { 188 $questionId = $this->getCurrentQuestionId(); 189 190 $this->getQuestionInstance($questionId)->removeIntermediateSolution( 191 $this->testSession->getActiveId(), 192 $this->testSession->getPass() 193 ); 194 } 195 // fau. 196 197 /** 198 * saves the user input of a question 199 */ 200 abstract public function saveQuestionSolution($authorized = true, $force = false); 201 202 abstract protected function canSaveResult(); 203 204 public function suspendTestCmd() 205 { 206 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen"); 207 } 208 209 /** 210 * Checks wheather the maximum processing time is reached or not 211 * 212 * Checks wheather the maximum processing time is reached or not 213 * 214 * @return bool TRUE if the maximum processing time is reached, FALSE otherwise 215 */ 216 public function isMaxProcessingTimeReached() 217 { 218 global $DIC; 219 $ilUser = $DIC['ilUser']; 220 $active_id = $this->testSession->getActiveId(); 221 $starting_time = $this->object->getStartingTimeOfUser($active_id); 222 if ($starting_time === false) { 223 return false; 224 } else { 225 return $this->object->isMaxProcessingTimeReached($starting_time, $active_id); 226 } 227 } 228 229 protected function determineInlineScoreDisplay() 230 { 231 $show_question_inline_score = false; 232 if ($this->object->getAnswerFeedbackPoints()) { 233 $show_question_inline_score = true; 234 return $show_question_inline_score; 235 } 236 return $show_question_inline_score; 237 } 238 239 protected function populateTestNavigationToolbar(ilTestNavigationToolbarGUI $toolbarGUI) 240 { 241 $this->tpl->setCurrentBlock('test_nav_toolbar'); 242 $this->tpl->setVariable('TEST_NAV_TOOLBAR', $toolbarGUI->getHTML()); 243 $this->tpl->parseCurrentBlock(); 244 } 245 246 protected function populateQuestionNavigation($sequenceElement, $disabled, $primaryNext) 247 { 248 if (!$this->isFirstQuestionInSequence($sequenceElement)) { 249 $this->populatePreviousButtons($disabled); 250 } 251 252 if (!$this->isLastQuestionInSequence($sequenceElement)) { 253 $this->populateNextButtons($disabled, $primaryNext); 254 } 255 } 256 257 protected function populatePreviousButtons($disabled) 258 { 259 $this->populateUpperPreviousButtonBlock($disabled); 260 $this->populateLowerPreviousButtonBlock($disabled); 261 } 262 263 protected function populateNextButtons($disabled, $primaryNext) 264 { 265 $this->populateUpperNextButtonBlock($disabled, $primaryNext); 266 $this->populateLowerNextButtonBlock($disabled, $primaryNext); 267 } 268 269 protected function populateLowerNextButtonBlock($disabled, $primaryNext) 270 { 271 $button = $this->buildNextButtonInstance($disabled, $primaryNext); 272 $button->setId('bottomnextbutton'); 273 274 $this->tpl->setCurrentBlock("next_bottom"); 275 $this->tpl->setVariable("BTN_NEXT", $button->render()); 276 $this->tpl->parseCurrentBlock(); 277 } 278 279 protected function populateUpperNextButtonBlock($disabled, $primaryNext) 280 { 281 $button = $this->buildNextButtonInstance($disabled, $primaryNext); 282 $button->setId('nextbutton'); 283 284 $this->tpl->setCurrentBlock("next"); 285 $this->tpl->setVariable("BTN_NEXT", $button->render()); 286 $this->tpl->parseCurrentBlock(); 287 } 288 289 protected function populateLowerPreviousButtonBlock($disabled) 290 { 291 $button = $this->buildPreviousButtonInstance($disabled); 292 $button->setId('bottomprevbutton'); 293 294 $this->tpl->setCurrentBlock("prev_bottom"); 295 $this->tpl->setVariable("BTN_PREV", $button->render()); 296 $this->tpl->parseCurrentBlock(); 297 } 298 299 protected function populateUpperPreviousButtonBlock($disabled) 300 { 301 $button = $this->buildPreviousButtonInstance($disabled); 302 $button->setId('prevbutton'); 303 304 $this->tpl->setCurrentBlock("prev"); 305 $this->tpl->setVariable("BTN_PREV", $button->render()); 306 $this->tpl->parseCurrentBlock(); 307 } 308 309 /** 310 * @param bool $disabled 311 * @param bool $primaryNext 312 * @return ilButtonBase|ilLinkButton|ilTestPlayerNavButton 313 */ 314 private function buildNextButtonInstance($disabled, $primaryNext) 315 { 316 $button = ilTestPlayerNavButton::getInstance(); 317 // fau: testNav - set glyphicon and primary 318 $button->setPrimary($primaryNext); 319 $button->setRightGlyph('glyphicon glyphicon-arrow-right'); 320 // fau. 321 $button->setNextCommand(ilTestPlayerCommands::NEXT_QUESTION); 322 $button->setUrl($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::NEXT_QUESTION)); 323 $button->setCaption('next_question'); 324 $button->addCSSClass('ilTstNavElem'); 325 //$button->setDisabled($disabled); 326 return $button; 327 } 328 329 /** 330 * @param $disabled 331 * @return ilTestPlayerNavButton 332 */ 333 private function buildPreviousButtonInstance($disabled) 334 { 335 $button = ilTestPlayerNavButton::getInstance(); 336 // fau: testNav - set glyphicon and primary 337 $button->setLeftGlyph('glyphicon glyphicon-arrow-left'); 338 // fau. 339 $button->setNextCommand(ilTestPlayerCommands::PREVIOUS_QUESTION); 340 $button->setUrl($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::PREVIOUS_QUESTION)); 341 $button->setCaption('previous_question'); 342 $button->addCSSClass('ilTstNavElem'); 343 //$button->setDisabled($disabled); 344 return $button; 345 } 346 347 protected function populateSpecificFeedbackBlock(assQuestionGUI $question_gui) 348 { 349 $solutionValues = $question_gui->object->getSolutionValues( 350 $this->testSession->getActiveId(), 351 null 352 ); 353 354 $feedback = $question_gui->getSpecificFeedbackOutput( 355 $question_gui->object->fetchIndexedValuesFromValuePairs($solutionValues) 356 ); 357 358 $this->tpl->setCurrentBlock("specific_feedback"); 359 $this->tpl->setVariable("SPECIFIC_FEEDBACK", $feedback); 360 $this->tpl->parseCurrentBlock(); 361 } 362 363 protected function populateGenericFeedbackBlock(assQuestionGUI $question_gui, $solutionCorrect) 364 { 365 $feedback = $question_gui->getGenericFeedbackOutput($this->testSession->getActiveId(), null); 366 367 if (strlen($feedback)) { 368 $cssClass = ( 369 $solutionCorrect ? 370 ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_CORRECT : ilAssQuestionFeedback::CSS_CLASS_FEEDBACK_WRONG 371 ); 372 373 $this->tpl->setCurrentBlock("answer_feedback"); 374 $this->tpl->setVariable("ANSWER_FEEDBACK", $feedback); 375 $this->tpl->setVariable("ILC_FB_CSS_CLASS", $cssClass); 376 $this->tpl->parseCurrentBlock(); 377 } 378 } 379 380 protected function populateScoreBlock($reachedPoints, $maxPoints) 381 { 382 $scoreInformation = sprintf( 383 $this->lng->txt("you_received_a_of_b_points"), 384 $reachedPoints, 385 $maxPoints 386 ); 387 388 $this->tpl->setCurrentBlock("received_points_information"); 389 $this->tpl->setVariable("RECEIVED_POINTS_INFORMATION", $scoreInformation); 390 $this->tpl->parseCurrentBlock(); 391 } 392 393 protected function populateSolutionBlock($solutionoutput) 394 { 395 if (strlen($solutionoutput)) { 396 $this->tpl->setCurrentBlock("solution_output"); 397 $this->tpl->setVariable("CORRECT_SOLUTION", $this->lng->txt("tst_best_solution_is")); 398 $this->tpl->setVariable("QUESTION_FEEDBACK", $solutionoutput); 399 $this->tpl->parseCurrentBlock(); 400 } 401 } 402 403 protected function populateSyntaxStyleBlock() 404 { 405 $this->tpl->setCurrentBlock("SyntaxStyle"); 406 $this->tpl->setVariable( 407 "LOCATION_SYNTAX_STYLESHEET", 408 ilObjStyleSheet::getSyntaxStylePath() 409 ); 410 $this->tpl->parseCurrentBlock(); 411 } 412 413 protected function populateContentStyleBlock() 414 { 415 include_once("./Services/Style/Content/classes/class.ilObjStyleSheet.php"); 416 $this->tpl->setCurrentBlock("ContentStyle"); 417 $this->tpl->setVariable( 418 "LOCATION_CONTENT_STYLESHEET", 419 ilObjStyleSheet::getContentStylePath(0) 420 ); 421 $this->tpl->parseCurrentBlock(); 422 } 423 424 /** 425 * Sets a session variable with the test access code for an anonymous test user 426 * 427 * Sets a session variable with the test access code for an anonymous test user 428 */ 429 public function setAnonymousIdCmd() 430 { 431 if ($this->testSession->isAnonymousUser()) { 432 $this->testSession->setAccessCodeToSession($_POST['anonymous_id']); 433 } 434 435 $this->ctrl->redirectByClass("ilobjtestgui", "infoScreen"); 436 } 437 438 /** 439 * Start a test for the first time 440 * 441 * Start a test for the first time. This method contains a lock 442 * to prevent multiple submissions by the start test button 443 */ 444 protected function startPlayerCmd() 445 { 446 $testStartLock = $this->getLockParameter(); 447 $isFirstTestStartRequest = false; 448 449 $this->processLocker->executeTestStartLockOperation(function () use ($testStartLock, &$isFirstTestStartRequest) { 450 if ($this->testSession->lookupTestStartLock() != $testStartLock) { 451 $this->testSession->persistTestStartLock($testStartLock); 452 $isFirstTestStartRequest = true; 453 } 454 }); 455 456 if ($isFirstTestStartRequest) { 457 $this->handleUserSettings(); 458 $this->ctrl->redirect($this, ilTestPlayerCommands::INIT_TEST); 459 } 460 461 $this->ctrl->setParameterByClass('ilObjTestGUI', 'lock', $testStartLock); 462 $this->ctrl->redirectByClass("ilobjtestgui", "redirectToInfoScreen"); 463 } 464 465 public function getLockParameter() 466 { 467 if (isset($_POST['lock']) && strlen($_POST['lock'])) { 468 return $_POST['lock']; 469 } elseif (isset($_GET['lock']) && strlen($_GET['lock'])) { 470 return $_GET['lock']; 471 } 472 473 return null; 474 } 475 476 /** 477 * Resume a test at the last position 478 */ 479 abstract protected function resumePlayerCmd(); 480 481 /** 482 * Start a test for the first time after a redirect 483 */ 484 protected function initTestCmd() 485 { 486 if ($this->object->checkMaximumAllowedUsers() == false) { 487 return $this->showMaximumAllowedUsersReachedMessage(); 488 } 489 490 if ($this->testSession->isAnonymousUser() && !$this->testSession->getActiveId()) { 491 $accessCode = $this->testSession->createNewAccessCode(); 492 493 $this->testSession->setAccessCodeToSession($accessCode); 494 $this->testSession->setAnonymousId($accessCode); 495 $this->testSession->saveToDb(); 496 497 $this->ctrl->redirect($this, ilTestPlayerCommands::DISPLAY_ACCESS_CODE); 498 } 499 500 $this->testSession->unsetAccessCodeInSession(); 501 $this->ctrl->redirect($this, ilTestPlayerCommands::START_TEST); 502 } 503 504 public function displayAccessCodeCmd() 505 { 506 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_anonymous_code_presentation.html", "Modules/Test"); 507 $this->tpl->setCurrentBlock("adm_content"); 508 $this->tpl->setVariable("TEXT_ANONYMOUS_CODE_CREATED", $this->lng->txt("tst_access_code_created")); 509 $this->tpl->setVariable("TEXT_ANONYMOUS_CODE", $this->testSession->getAccessCodeFromSession()); 510 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this)); 511 $this->tpl->setVariable("CMD_CONFIRM", ilTestPlayerCommands::ACCESS_CODE_CONFIRMED); 512 $this->tpl->setVariable("TXT_CONFIRM", $this->lng->txt("continue_work")); 513 $this->tpl->parseCurrentBlock(); 514 } 515 516 public function accessCodeConfirmedCmd() 517 { 518 $this->ctrl->redirect($this, ilTestPlayerCommands::START_TEST); 519 } 520 521 /** 522 * Handles some form parameters on starting and resuming a test 523 */ 524 public function handleUserSettings() 525 { 526 global $DIC; 527 $ilUser = $DIC['ilUser']; 528 529 if ($_POST["chb_javascript"]) { 530 $ilUser->writePref("tst_javascript", 1); 531 } else { 532 $ilUser->writePref("tst_javascript", 0); 533 } 534 535 // hide previous results 536 if ($this->object->getNrOfTries() != 1) { 537 if ($this->object->getUsePreviousAnswers() == 1) { 538 if ($_POST["chb_use_previous_answers"]) { 539 $ilUser->writePref("tst_use_previous_answers", 1); 540 } else { 541 $ilUser->writePref("tst_use_previous_answers", 0); 542 } 543 } 544 } 545 } 546 547 /** 548 * Redirect the user after an automatic save when the time limit is reached 549 * @throws ilTestException 550 */ 551 public function redirectAfterAutosaveCmd() 552 { 553 $active_id = $this->testSession->getActiveId(); 554 $actualpass = ilObjTest::_getPass($active_id); 555 556 $this->performTestPassFinishedTasks($actualpass); 557 558 $this->testSession->setLastFinishedPass($this->testSession->getPass()); 559 $this->testSession->increaseTestPass(); 560 561 $url = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED, '', false, false); 562 563 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_redirect_autosave.html", "Modules/Test"); 564 $this->tpl->setVariable("TEXT_REDIRECT", $this->lng->txt("redirectAfterSave")); 565 $this->tpl->setVariable("URL", $url); 566 } 567 568 public function redirectAfterDashboardCmd() 569 { 570 $active_id = $this->testSession->getActiveId(); 571 $actualpass = ilObjTest::_getPass($active_id); 572 573 $this->performTestPassFinishedTasks($actualpass); 574 575 $this->testSession->setLastFinishedPass($this->testSession->getPass()); 576 $this->testSession->increaseTestPass(); 577 578 $url = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED, '', false, false); 579 580 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_redirect_autosave.html", "Modules/Test"); 581 $this->tpl->setVariable("TEXT_REDIRECT", $this->lng->txt("redirectAfterSave")); 582 $this->tpl->setVariable("URL", $url); 583 } 584 585 abstract protected function getCurrentQuestionId(); 586 587 /** 588 * Automatically save a user answer while working on the test 589 * (called repeatedly by asynchronous posts in configured autosave interval) 590 */ 591 public function autosaveCmd() 592 { 593 $result = ""; 594 if (is_array($_POST) && count($_POST) > 0) { 595 if (!$this->canSaveResult() || $this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { 596 $result = '-IGNORE-'; 597 } else { 598 // answer is changed from authorized solution, so save the change as intermediate solution 599 if ($this->getAnswerChangedParameter()) { 600 $res = $this->saveQuestionSolution(false, true); 601 } 602 // answer is not changed from authorized solution, so delete an intermediate solution 603 else { 604 $db_res = $this->removeIntermediateSolution(); 605 $res = is_int($db_res); 606 } 607 if ($res) { 608 $result = $this->lng->txt("autosave_success"); 609 } else { 610 $result = $this->lng->txt("autosave_failed"); 611 } 612 } 613 } 614 echo $result; 615 exit; 616 } 617 618 /** 619 * Automatically save a user answer when the limited duration of a test run is reached 620 * (called by synchronous form submit when the remaining time count down reaches zero) 621 */ 622 public function autosaveOnTimeLimitCmd() 623 { 624 if (!$this->isParticipantsAnswerFixed($this->getCurrentQuestionId())) { 625 // time limit saves the user solution as authorized 626 $this->saveQuestionSolution(true, true); 627 } 628 $this->ctrl->redirect($this, ilTestPlayerCommands::REDIRECT_ON_TIME_LIMIT); 629 } 630 631 632 // fau: testNav - new function detectChangesCmd() 633 /** 634 * Detect changes sent in the background to the server 635 * This is called by ajax from ilTestPlayerQuestionEditControl.js 636 * It is needed by Java and Flash question and eventually plgin question vtypes 637 */ 638 protected function detectChangesCmd() 639 { 640 $questionId = $this->getCurrentQuestionId(); 641 $state = $this->getQuestionInstance($questionId)->lookupForExistingSolutions( 642 $this->testSession->getActiveId(), 643 $this->testSession->getPass() 644 ); 645 $result = array(); 646 $result['isAnswered'] = $state['authorized']; 647 $result['isAnswerChanged'] = $state['intermediate']; 648 649 echo json_encode($result); 650 exit; 651 } 652 // fau. 653 654 protected function submitIntermediateSolutionCmd() 655 { 656 $this->saveQuestionSolution(false, true); 657 // fau: testNav - set the 'answer changed' parameter when an intermediate solution is submitted 658 $this->setAnswerChangedParameter(true); 659 // fau. 660 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 661 } 662 663 /** 664 * Toggle side list 665 */ 666 public function toggleSideListCmd() 667 { 668 global $DIC; 669 $ilUser = $DIC['ilUser']; 670 671 $show_side_list = $ilUser->getPref('side_list_of_questions'); 672 $ilUser->writePref('side_list_of_questions', !$show_side_list); 673 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 674 } 675 676 protected function markQuestionAndSaveIntermediateCmd() 677 { 678 // fau: testNav - handle intermediate submit when marking the question 679 $this->handleIntermediateSubmit(); 680 // fau. 681 $this->markQuestionCmd(); 682 } 683 684 /** 685 * Set a question solved 686 */ 687 protected function markQuestionCmd() 688 { 689 $questionId = $this->testSequence->getQuestionForSequence( 690 $this->getCurrentSequenceElement() 691 ); 692 693 $this->object->setQuestionSetSolved(1, $questionId, $this->testSession->getUserId()); 694 695 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 696 } 697 698 protected function unmarkQuestionAndSaveIntermediateCmd() 699 { 700 // fau: testNav - handle intermediate submit when unmarking the question 701 $this->handleIntermediateSubmit(); 702 // fau. 703 $this->unmarkQuestionCmd(); 704 } 705 706 /** 707 * Set a question unsolved 708 */ 709 protected function unmarkQuestionCmd() 710 { 711 $questionId = $this->testSequence->getQuestionForSequence( 712 $this->getCurrentSequenceElement() 713 ); 714 715 $this->object->setQuestionSetSolved(0, $questionId, $this->testSession->getUserId()); 716 717 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 718 } 719 720 /** 721 * The final submission of a test was confirmed 722 */ 723 protected function confirmFinishCmd() 724 { 725 $this->finishTestCmd(false); 726 } 727 728 /** 729 * Confirmation of the tests final submission 730 */ 731 protected function confirmFinishTestCmd() 732 { 733 /** 734 * @var $ilUser ilObjUser 735 */ 736 global $DIC; 737 $ilUser = $DIC['ilUser']; 738 739 require_once 'Services/Utilities/classes/class.ilConfirmationGUI.php'; 740 $confirmation = new ilConfirmationGUI(); 741 $confirmation->setFormAction($this->ctrl->getFormAction($this, 'confirmFinish')); 742 $confirmation->setHeaderText($this->lng->txt("tst_finish_confirmation_question")); 743 $confirmation->setConfirm($this->lng->txt("tst_finish_confirm_button"), 'confirmFinish'); 744 $confirmation->setCancel($this->lng->txt("tst_finish_confirm_cancel_button"), ilTestPlayerCommands::BACK_FROM_FINISHING); 745 746 $this->populateHelperGuiContent($confirmation); 747 } 748 749 public function finishTestCmd($requires_confirmation = true) 750 { 751 unset($_SESSION["tst_next"]); 752 753 $active_id = $this->testSession->getActiveId(); 754 $actualpass = ilObjTest::_getPass($active_id); 755 756 $allObligationsAnswered = ilObjTest::allObligationsAnswered($this->testSession->getTestId(), $active_id, $actualpass); 757 758 /* 759 * The following "endgames" are possible prior to actually finishing the test: 760 * - Obligations (Ability to finish the test.) 761 * If not all obligatory questions are answered, the user is presented a list 762 * showing the deficits. 763 * - Examview (Will to finish the test.) 764 * With the examview, the participant can review all answers given in ILIAS or a PDF prior to 765 * commencing to the finished test. 766 * - Last pass allowed (Reassuring the will to finish the test.) 767 * If passes are limited, on the last pass, an additional confirmation is to be displayed. 768 */ 769 770 771 if ($this->object->areObligationsEnabled() && !$allObligationsAnswered) { 772 if ($this->object->getListOfQuestions()) { 773 $this->ctrl->redirect($this, ilTestPlayerCommands::QUESTION_SUMMARY_INC_OBLIGATIONS); 774 } else { 775 $this->ctrl->redirect($this, ilTestPlayerCommands::QUESTION_SUMMARY_OBLIGATIONS_ONLY); 776 } 777 778 return; 779 } 780 781 // Examview enabled & !reviewed & requires_confirmation? test_submission_overview (review gui) 782 if ($this->object->getEnableExamview() && !isset($_GET['reviewed']) && $requires_confirmation) { 783 $this->ctrl->redirectByClass('ilTestSubmissionReviewGUI', "show"); 784 return; 785 } 786 787 // Last try in limited tries & !confirmed 788 if (($requires_confirmation) && ($actualpass == $this->object->getNrOfTries() - 1)) { 789 // show confirmation page 790 return $this->confirmFinishTestCmd(); 791 } 792 793 // Last try in limited tries & confirmed? 794 if (($actualpass == $this->object->getNrOfTries() - 1) && (!$requires_confirmation)) { 795 // @todo: php7 ask mister test 796 #$ilAuth->setIdle(ilSession::getIdleValue(), false); 797 #$ilAuth->setExpire(0); 798 switch ($this->object->getMailNotification()) { 799 case 1: 800 $this->object->sendSimpleNotification($active_id); 801 break; 802 case 2: 803 $this->object->sendAdvancedNotification($active_id); 804 break; 805 } 806 } 807 808 // Non-last try finish 809 if (!$_SESSION['tst_pass_finish']) { 810 if (!$_SESSION['tst_pass_finish']) { 811 $_SESSION['tst_pass_finish'] = 1; 812 } 813 if ($this->object->getMailNotificationType() == 1) { 814 switch ($this->object->getMailNotification()) { 815 case 1: 816 $this->object->sendSimpleNotification($active_id); 817 break; 818 case 2: 819 $this->object->sendAdvancedNotification($active_id); 820 break; 821 } 822 } 823 } 824 825 // no redirect request loops after test pass finished tasks has been performed 826 827 $this->performTestPassFinishedTasks($actualpass); 828 829 $this->ctrl->redirect($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED); 830 } 831 832 protected function performTestPassFinishedTasks($finishedPass) 833 { 834 require_once 'Modules/Test/classes/class.ilTestPassFinishTasks.php'; 835 836 $finishTasks = new ilTestPassFinishTasks($this->testSession->getActiveId(), $this->object->getId()); 837 $finishTasks->performFinishTasks($this->processLocker); 838 } 839 840 protected function afterTestPassFinishedCmd() 841 { 842 $activeId = $this->testSession->getActiveId(); 843 $lastFinishedPass = $this->testSession->getLastFinishedPass(); 844 845 // handle test signature 846 if ($this->isTestSignRedirectRequired($activeId, $lastFinishedPass)) { 847 $this->ctrl->redirectByClass('ilTestSignatureGUI', 'invokeSignaturePlugin'); 848 } 849 850 // show final statement 851 if (!$_GET['skipfinalstatement']) { 852 if ($this->object->getShowFinalStatement()) { 853 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_FINAL_STATMENT); 854 } 855 } 856 857 // redirect after test 858 $redirection_mode = $this->object->getRedirectionMode(); 859 $redirection_url = $this->object->getRedirectionUrl(); 860 if ($redirection_url && $redirection_mode) { 861 if ($redirection_mode == REDIRECT_KIOSK) { 862 if ($this->object->getKioskMode()) { 863 ilUtil::redirect($redirection_url); 864 } 865 } else { 866 ilUtil::redirect($redirection_url); 867 } 868 } 869 870 // default redirect (pass overview when enabled, otherwise infoscreen) 871 $this->redirectBackCmd(); 872 } 873 874 protected function isTestSignRedirectRequired($activeId, $lastFinishedPass) 875 { 876 if (!$this->object->getSignSubmission()) { 877 return false; 878 } 879 880 if (!is_null(ilSession::get("signed_{$activeId}_{$lastFinishedPass}"))) { 881 return false; 882 } 883 884 global $DIC; 885 $ilPluginAdmin = $DIC['ilPluginAdmin']; 886 887 $activePlugins = $ilPluginAdmin->getActivePluginsForSlot(IL_COMP_MODULE, 'Test', 'tsig'); 888 889 if (!count($activePlugins)) { 890 return false; 891 } 892 893 return true; 894 } 895 896 /** 897 * @param $active 898 * 899 * @return void 900 */ 901 protected function archiveParticipantSubmission($active, $pass) 902 { 903 global $DIC; 904 $ilObjDataCache = $DIC['ilObjDataCache']; 905 906 require_once 'Modules/Test/classes/class.ilTestResultHeaderLabelBuilder.php'; 907 $testResultHeaderLabelBuilder = new ilTestResultHeaderLabelBuilder($this->lng, $ilObjDataCache); 908 909 $objectivesList = null; 910 911 if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) { 912 $testSequence = $this->testSequenceFactory->getSequenceByActiveIdAndPass($this->testSession->getActiveId(), $this->testSession->getPass()); 913 $testSequence->loadFromDb(); 914 $testSequence->loadQuestions(); 915 916 require_once 'Modules/Course/classes/Objectives/class.ilLOTestQuestionAdapter.php'; 917 $objectivesAdapter = ilLOTestQuestionAdapter::getInstance($this->testSession); 918 919 $objectivesList = $this->buildQuestionRelatedObjectivesList($objectivesAdapter, $testSequence); 920 $objectivesList->loadObjectivesTitles(); 921 922 $testResultHeaderLabelBuilder->setObjectiveOrientedContainerId($this->testSession->getObjectiveOrientedContainerId()); 923 $testResultHeaderLabelBuilder->setUserId($this->testSession->getUserId()); 924 $testResultHeaderLabelBuilder->setTestObjId($this->object->getId()); 925 $testResultHeaderLabelBuilder->setTestRefId($this->object->getRefId()); 926 $testResultHeaderLabelBuilder->initObjectiveOrientedMode(); 927 } 928 929 $results = $this->object->getTestResult( 930 $active, 931 $pass, 932 false, 933 !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired() 934 ); 935 936 require_once 'class.ilTestEvaluationGUI.php'; 937 $testevaluationgui = new ilTestEvaluationGUI($this->object); 938 $results_output = $testevaluationgui->getPassListOfAnswers( 939 $results, 940 $active, 941 $pass, 942 false, 943 false, 944 false, 945 false, 946 false, 947 $objectivesList, 948 $testResultHeaderLabelBuilder 949 ); 950 951 require_once './Modules/Test/classes/class.ilTestArchiver.php'; 952 global $DIC; 953 $ilSetting = $DIC['ilSetting']; 954 $inst_id = $ilSetting->get('inst_id', null); 955 $archiver = new ilTestArchiver($this->object->getId()); 956 957 $path = ilUtil::getWebspaceDir() . '/assessment/' . $this->object->getId() . '/exam_pdf'; 958 if (!is_dir($path)) { 959 ilUtil::makeDirParents($path); 960 } 961 $filename = realpath($path) . '/exam_N' . $inst_id . '-' . $this->object->getId() 962 . '-' . $active . '-' . $pass . '.pdf'; 963 964 ilTestPDFGenerator::generatePDF($results_output, ilTestPDFGenerator::PDF_OUTPUT_FILE, $filename); 965 //$template->setVariable("PDF_FILE_LOCATION", $filename); 966 // Participant submission 967 $archiver->handInParticipantSubmission($active, $pass, $filename, $results_output); 968 //$archiver->handInParticipantMisc( $active, $pass, 'signature_gedoens.sig', $filename ); 969 //$archiver->handInParticipantQuestionMaterial( $active, $pass, 123, 'file_upload.pdf', $filename ); 970 971 global $DIC; 972 $ilias = $DIC['ilias']; 973 $questions = $this->object->getQuestions(); 974 foreach ($questions as $question_id) { 975 $question_object = $this->object->getQuestionDataset($question_id); 976 if ($question_object->type_tag == 'assFileUpload') { 977 // Pfad: /data/default/assessment/tst_2/14/21/files/file_14_4_1370417414.png 978 // /data/ - klar 979 // /assessment/ - Konvention 980 // /tst_2/ = /tst_<test_id> (ilObjTest) 981 // /14/ = /<active_fi>/ 982 // /21/ = /<question_id>/ (question_object) 983 // /files/ - Konvention 984 // file_14_4_1370417414.png = file_<active_fi>_<pass>_<some timestamp>.<ext> 985 986 $candidate_path = 987 $ilias->ini_ilias->readVariable('server', 'absolute_path') . ilTestArchiver::DIR_SEP 988 . $ilias->ini_ilias->readVariable('clients', 'path') . ilTestArchiver::DIR_SEP 989 . $ilias->client_id . ilTestArchiver::DIR_SEP 990 . 'assessment' . ilTestArchiver::DIR_SEP 991 . 'tst_' . $this->object->test_id . ilTestArchiver::DIR_SEP 992 . $active . ilTestArchiver::DIR_SEP 993 . $question_id . ilTestArchiver::DIR_SEP 994 . 'files' . ilTestArchiver::DIR_SEP; 995 $handle = opendir($candidate_path); 996 while ($handle !== false && ($file = readdir($handle)) !== false) { 997 if ($file != null) { 998 $filename_start = 'file_' . $active . '_' . $pass . '_'; 999 1000 if (strpos($file, $filename_start) === 0) { 1001 $archiver->handInParticipantQuestionMaterial($active, $pass, $question_id, $file, $file); 1002 } 1003 } 1004 } 1005 } 1006 } 1007 $passdata = $this->object->getTestResult( 1008 $active, 1009 $pass, 1010 false, 1011 !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired() 1012 ); 1013 $overview = $testevaluationgui->getPassListOfAnswers( 1014 $passdata, 1015 $active, 1016 $pass, 1017 true, 1018 false, 1019 false, 1020 true, 1021 false, 1022 $objectivesList, 1023 $testResultHeaderLabelBuilder 1024 ); 1025 $filename = realpath(ilUtil::getWebspaceDir()) . '/assessment/scores-' . $this->object->getId() . '-' . $active . '-' . $pass . '.pdf'; 1026 ilTestPDFGenerator::generatePDF($overview, ilTestPDFGenerator::PDF_OUTPUT_FILE, $filename); 1027 $archiver->handInTestResult($active, $pass, $filename); 1028 unlink($filename); 1029 1030 return; 1031 } 1032 1033 public function redirectBackCmd() 1034 { 1035 global $DIC; /* @var ILIAS\DI\Container $DIC */ 1036 require_once 'Modules/Test/classes/class.ilTestPassesSelector.php'; 1037 $testPassesSelector = new ilTestPassesSelector($DIC['ilDB'], $this->object); 1038 $testPassesSelector->setActiveId($this->testSession->getActiveId()); 1039 $testPassesSelector->setLastFinishedPass($this->testSession->getLastFinishedPass()); 1040 1041 if (count($testPassesSelector->getReportablePasses())) { 1042 if ($this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired()) { 1043 $this->ctrl->redirectByClass(array('ilTestResultsGUI', 'ilTestEvalObjectiveOrientedGUI')); 1044 } 1045 1046 $this->ctrl->redirectByClass(array('ilTestResultsGUI', 'ilMyTestResultsGUI', 'ilTestEvaluationGUI')); 1047 } 1048 1049 $this->backToInfoScreenCmd(); 1050 } 1051 1052 protected function backToInfoScreenCmd() 1053 { 1054 $this->ctrl->redirectByClass('ilObjTestGUI', 'redirectToInfoScreen'); 1055 } 1056 1057 /* 1058 * Presents the final statement of a test 1059 */ 1060 public function showFinalStatementCmd() 1061 { 1062 $template = new ilTemplate("tpl.il_as_tst_final_statement.html", true, true, "Modules/Test"); 1063 $this->ctrl->setParameter($this, "skipfinalstatement", 1); 1064 $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this, ilTestPlayerCommands::AFTER_TEST_PASS_FINISHED)); 1065 $template->setVariable("FINALSTATEMENT", $this->object->prepareTextareaOutput($this->object->getFinalStatement(), true)); 1066 $template->setVariable("BUTTON_CONTINUE", $this->lng->txt("btn_next")); 1067 $this->tpl->setVariable($this->getContentBlockName(), $template->get()); 1068 } 1069 1070 public function getKioskHead() 1071 { 1072 global $DIC; 1073 $ilUser = $DIC['ilUser']; 1074 1075 $template = new ilTemplate('tpl.il_as_tst_kiosk_head.html', true, true, 'Modules/Test'); 1076 if ($this->object->getShowKioskModeTitle()) { 1077 $template->setCurrentBlock("kiosk_show_title"); 1078 $template->setVariable("TEST_TITLE", $this->object->getTitle()); 1079 $template->parseCurrentBlock(); 1080 } 1081 if ($this->object->getShowKioskModeParticipant()) { 1082 $template->setCurrentBlock("kiosk_show_participant"); 1083 $template->setVariable("PARTICIPANT_NAME_TXT", $this->lng->txt("login_as")); 1084 $template->setVariable("PARTICIPANT_NAME", $ilUser->getFullname()); 1085 $template->setVariable("PARTICIPANT_LOGIN", $ilUser->getLogin()); 1086 $template->setVariable("PARTICIPANT_MATRICULATION", $ilUser->getMatriculation()); 1087 $template->setVariable("PARTICIPANT_EMAIL", $ilUser->getEmail()); 1088 $template->parseCurrentBlock(); 1089 } 1090 if ($this->object->isShowExamIdInTestPassEnabled()) { 1091 $exam_id = ilObjTest::buildExamId( 1092 $this->testSession->getActiveId(), 1093 $this->testSession->getPass(), 1094 $this->object->getId() 1095 ); 1096 1097 $template->setCurrentBlock("kiosk_show_exam_id"); 1098 $template->setVariable("EXAM_ID_TXT", $this->lng->txt("exam_id")); 1099 $template->setVariable("EXAM_ID", $exam_id); 1100 $template->parseCurrentBlock(); 1101 } 1102 return $template->get(); 1103 } 1104 1105 /** 1106 * @return string $formAction 1107 */ 1108 protected function prepareTestPage($presentationMode, $sequenceElement, $questionId) 1109 { 1110 global $DIC; 1111 $ilUser = $DIC['ilUser']; 1112 $ilNavigationHistory = $DIC['ilNavigationHistory']; 1113 1114 $ilNavigationHistory->addItem( 1115 $this->testSession->getRefId(), 1116 $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::RESUME_PLAYER), 1117 'tst' 1118 ); 1119 1120 $this->initTestPageTemplate(); 1121 $this->populateContentStyleBlock(); 1122 $this->populateSyntaxStyleBlock(); 1123 1124 if ($this->isMaxProcessingTimeReached()) { 1125 $this->maxProcessingTimeReached(); 1126 return; 1127 } 1128 1129 if ($this->object->endingTimeReached()) { 1130 $this->endingTimeReached(); 1131 return; 1132 } 1133 1134 if ($this->isOptionalQuestionAnsweringConfirmationRequired($sequenceElement)) { 1135 $this->ctrl->setParameter($this, "sequence", $sequenceElement); 1136 $this->showAnswerOptionalQuestionsConfirmation(); 1137 return; 1138 } 1139 1140 if ($this->object->getKioskMode()) { 1141 $this->populateKioskHead(); 1142 } 1143 1144 $this->tpl->setVariable("TEST_ID", $this->object->getTestId()); 1145 $this->tpl->setVariable("LOGIN", $ilUser->getLogin()); 1146 $this->tpl->setVariable("SEQ_ID", $sequenceElement); 1147 $this->tpl->setVariable("QUEST_ID", $questionId); 1148 1149 if ($this->object->getEnableProcessingTime()) { 1150 $this->outProcessingTime($this->testSession->getActiveId()); 1151 } 1152 1153 $this->tpl->setVariable("PAGETITLE", "- " . $this->object->getTitle()); 1154 1155 if ($this->object->isShowExamIdInTestPassEnabled() && !$this->object->getKioskMode()) { 1156 $this->tpl->setCurrentBlock('exam_id_footer'); 1157 $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId( 1158 $this->testSession->getActiveId(), 1159 $this->testSession->getPass(), 1160 $this->object->getId() 1161 )); 1162 $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id')); 1163 $this->tpl->parseCurrentBlock(); 1164 } 1165 1166 if ($this->object->getListOfQuestions()) { 1167 $this->showSideList($presentationMode, $sequenceElement); 1168 } 1169 } 1170 1171 abstract protected function isOptionalQuestionAnsweringConfirmationRequired($sequenceElement); 1172 1173 abstract protected function isShowingPostponeStatusReguired($questionId); 1174 1175 protected function showQuestionViewable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse) 1176 { 1177 $questionNavigationGUI = $this->buildReadOnlyStateQuestionNavigationGUI($questionGui->object->getId()); 1178 $questionNavigationGUI->setQuestionWorkedThrough($isQuestionWorkedThrough); 1179 $questionGui->setNavigationGUI($questionNavigationGUI); 1180 1181 // fau: testNav - set answere status in question header 1182 $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered($isQuestionWorkedThrough); 1183 // fau. 1184 1185 $answerFeedbackEnabled = ( 1186 $instantResponse && $this->object->getSpecificAnswerFeedback() 1187 ); 1188 1189 $solutionoutput = $questionGui->getSolutionOutput( 1190 $this->testSession->getActiveId(), #active_id 1191 $this->testSession->getPass(), #pass 1192 false, #graphical_output 1193 false, #result_output 1194 true, #show_question_only 1195 $answerFeedbackEnabled, #show_feedback 1196 false, #show_correct_solution 1197 false, #show_manual_scoring 1198 true #show_question_text 1199 ); 1200 1201 $pageoutput = $questionGui->outQuestionPage( 1202 "", 1203 $this->isShowingPostponeStatusReguired($questionGui->object->getId()), 1204 $this->testSession->getActiveId(), 1205 $solutionoutput 1206 ); 1207 1208 $this->tpl->setCurrentBlock('readonly_css_class'); 1209 $this->tpl->touchBlock('readonly_css_class'); 1210 $this->tpl->parseCurrentBlock(); 1211 1212 $this->tpl->setVariable('QUESTION_OUTPUT', $pageoutput); 1213 1214 $this->tpl->setVariable("FORMACTION", $formAction); 1215 $this->tpl->setVariable("ENCTYPE", 'enctype="' . $questionGui->getFormEncodingType() . '"'); 1216 $this->tpl->setVariable("FORM_TIMESTAMP", time()); 1217 } 1218 1219 protected function showQuestionEditable(assQuestionGUI $questionGui, $formAction, $isQuestionWorkedThrough, $instantResponse) 1220 { 1221 $questionNavigationGUI = $this->buildEditableStateQuestionNavigationGUI( 1222 $questionGui->object->getId(), 1223 $this->populateCharSelectorIfRequired() 1224 ); 1225 if ($isQuestionWorkedThrough) { 1226 $questionNavigationGUI->setDiscardSolutionButtonEnabled(true); 1227 // fau: testNav - set answere status in question header 1228 $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true); 1229 // fau. 1230 } elseif ($this->object->isPostponingEnabled()) { 1231 $questionNavigationGUI->setSkipQuestionLinkTarget( 1232 $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::SKIP_QUESTION) 1233 ); 1234 } 1235 $questionGui->setNavigationGUI($questionNavigationGUI); 1236 1237 $isPostponed = $this->isShowingPostponeStatusReguired($questionGui->object->getId()); 1238 1239 $answerFeedbackEnabled = ( 1240 $instantResponse && $this->object->getSpecificAnswerFeedback() 1241 ); 1242 1243 if (isset($_GET['save_error']) && $_GET['save_error'] == 1 && isset($_SESSION['previouspost'])) { 1244 $userPostSolution = $_SESSION['previouspost']; 1245 unset($_SESSION['previouspost']); 1246 } else { 1247 $userPostSolution = false; 1248 } 1249 1250 // fau: testNav - add special checkbox for mc question 1251 // moved to another patch block 1252 // fau. 1253 1254 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly 1255 $qstConfig = $questionGui->object->getTestPresentationConfig(); 1256 1257 if ($questionGui instanceof assMultipleChoiceGUI) { 1258 $qstConfig->setWorkedThrough($isQuestionWorkedThrough); 1259 $qstConfig->setIsUnchangedAnswerPossible($this->object->getMCScoring()); 1260 } 1261 1262 if ($qstConfig->isPreviousPassSolutionReuseAllowed()) { 1263 $passIndex = $this->determineSolutionPassIndex($questionGui); // last pass having solution stored 1264 if ($passIndex < $this->testSession->getPass()) { // it's the previous pass if current pass is higher 1265 $qstConfig->setSolutionInitiallyPrefilled(true); 1266 } 1267 } else { 1268 $passIndex = $this->testSession->getPass(); 1269 } 1270 // hey. 1271 1272 // Answer specific feedback is rendered into the display of the test question with in the concrete question types outQuestionForTest-method. 1273 // Notation of the params prior to getting rid of this crap in favor of a class 1274 $questionGui->outQuestionForTest( 1275 $formAction, #form_action 1276 $this->testSession->getActiveId(), #active_id 1277 // hey: prevPassSolutions - prepared pass index having no, current or previous solution 1278 $passIndex, #pass 1279 // hey. 1280 $isPostponed, #is_postponed 1281 $userPostSolution, #user_post_solution 1282 $answerFeedbackEnabled #answer_feedback == inline_specific_feedback 1283 ); 1284 // The display of specific inline feedback and specific feedback in an own block is to honor questions, which 1285 // have the possibility to embed the specific feedback into their output while maintaining compatibility to 1286 // questions, which do not have such facilities. E.g. there can be no "specific inline feedback" for essay 1287 // questions, while the multiple-choice questions do well. 1288 1289 1290 $this->populateModals(); 1291 1292 // fau: testNav - pouplate the new question edit control instead of the deprecated intermediate solution saver 1293 $this->populateQuestionEditControl($questionGui); 1294 // fau. 1295 } 1296 1297 // hey: prevPassSolutions - determine solution pass index 1298 protected function determineSolutionPassIndex(assQuestionGUI $questionGui) 1299 { 1300 require_once './Modules/Test/classes/class.ilObjTest.php'; 1301 1302 if (ilObjTest::_getUsePreviousAnswers($this->testSession->getActiveId(), true)) { 1303 $currentSolutionAvailable = $questionGui->object->authorizedOrIntermediateSolutionExists( 1304 $this->testSession->getActiveId(), 1305 $this->testSession->getPass() 1306 ); 1307 1308 if (!$currentSolutionAvailable) { 1309 $previousPass = $questionGui->object->getSolutionMaxPass( 1310 $this->testSession->getActiveId() 1311 ); 1312 1313 $previousSolutionAvailable = $questionGui->object->authorizedSolutionExists( 1314 $this->testSession->getActiveId(), 1315 $previousPass 1316 ); 1317 1318 if ($previousSolutionAvailable) { 1319 return $previousPass; 1320 } 1321 } 1322 } 1323 1324 return $this->testSession->getPass(); 1325 } 1326 // hey. 1327 1328 abstract protected function showQuestionCmd(); 1329 1330 abstract protected function editSolutionCmd(); 1331 1332 abstract protected function submitSolutionCmd(); 1333 1334 // fau: testNav - new function to revert probably auto-saved changes and show the last submitted question state 1335 protected function revertChangesCmd() 1336 { 1337 $this->removeIntermediateSolution(); 1338 $this->setAnswerChangedParameter(false); 1339 $this->ctrl->saveParameter($this, 'sequence'); 1340 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 1341 } 1342 // fau. 1343 1344 abstract protected function discardSolutionCmd(); 1345 1346 abstract protected function skipQuestionCmd(); 1347 1348 abstract protected function startTestCmd(); 1349 /** 1350 * check access restrictions like client ip, partipating user etc. 1351 * 1352 * check access restrictions like client ip, partipating user etc. 1353 * 1354 * @access public 1355 */ 1356 public function checkOnlineTestAccess() 1357 { 1358 global $DIC; 1359 $ilUser = $DIC['ilUser']; 1360 1361 // check if user is invited to participate 1362 $user = $this->object->getInvitedUsers($ilUser->getId()); 1363 if (!is_array($user) || count($user) != 1) { 1364 ilUtil::sendInfo($this->lng->txt("user_not_invited"), true); 1365 $this->ctrl->redirectByClass("ilobjtestgui", "backToRepository"); 1366 } 1367 1368 $user = array_pop($user); 1369 // check if client ip is set and if current remote addr is equal to stored client-ip 1370 if (strcmp($user["clientip"], "") != 0 && strcmp($user["clientip"], $_SERVER["REMOTE_ADDR"]) != 0) { 1371 ilUtil::sendInfo($this->lng->txt("user_wrong_clientip"), true); 1372 $this->ctrl->redirectByClass("ilobjtestgui", "backToRepository"); 1373 } 1374 } 1375 1376 1377 /** 1378 * test accessible returns true if the user can perform the test 1379 */ 1380 public function isTestAccessible() 1381 { 1382 return !$this->isNrOfTriesReached() 1383 and !$this->isMaxProcessingTimeReached() 1384 and $this->object->startingTimeReached() 1385 and !$this->object->endingTimeReached(); 1386 } 1387 1388 /** 1389 * nr of tries exceeded 1390 */ 1391 public function isNrOfTriesReached() 1392 { 1393 return $this->object->hasNrOfTriesRestriction() && $this->object->isNrOfTriesReached($this->testSession->getPass()); 1394 } 1395 1396 /** 1397 * handle endingTimeReached 1398 * @private 1399 */ 1400 1401 public function endingTimeReached() 1402 { 1403 ilUtil::sendInfo(sprintf($this->lng->txt("detail_ending_time_reached"), ilDatePresentation::formatDate(new ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX)))); 1404 $this->testSession->increasePass(); 1405 $this->testSession->setLastSequence(0); 1406 $this->testSession->saveToDb(); 1407 1408 $this->redirectBackCmd(); 1409 } 1410 1411 /** 1412 * Outputs a message when the maximum processing time is reached 1413 * 1414 * Outputs a message when the maximum processing time is reached 1415 * 1416 * @access public 1417 */ 1418 public function maxProcessingTimeReached() 1419 { 1420 $this->suspendTestCmd(); 1421 } 1422 1423 /** 1424 * confirm submit results 1425 * if confirm then results are submitted and the screen will be redirected to the startpage of the test 1426 * @access public 1427 */ 1428 public function confirmSubmitAnswers() 1429 { 1430 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_submit_answers_confirm.html", "Modules/Test"); 1431 $this->tpl->setCurrentBlock("adm_content"); 1432 if ($this->object->isTestFinished($this->testSession->getActiveId())) { 1433 $this->tpl->setCurrentBlock("not_submit_allowed"); 1434 $this->tpl->setVariable("TEXT_ALREADY_SUBMITTED", $this->lng->txt("tst_already_submitted")); 1435 $this->tpl->setVariable("BTN_OK", $this->lng->txt("tst_show_answer_sheet")); 1436 } else { 1437 $this->tpl->setCurrentBlock("submit_allowed"); 1438 $this->tpl->setVariable("TEXT_CONFIRM_SUBMIT_RESULTS", $this->lng->txt("tst_confirm_submit_answers")); 1439 $this->tpl->setVariable("BTN_OK", $this->lng->txt("tst_submit_results")); 1440 } 1441 $this->tpl->setVariable("BTN_BACK", $this->lng->txt("back")); 1442 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this, "finalSubmission")); 1443 $this->tpl->parseCurrentBlock(); 1444 } 1445 1446 public function outProcessingTime($active_id) 1447 { 1448 global $DIC; 1449 $ilUser = $DIC['ilUser']; 1450 1451 $starting_time = $this->object->getStartingTimeOfUser($active_id); 1452 $processing_time = $this->object->getProcessingTimeInSeconds($active_id); 1453 $processing_time_minutes = floor($processing_time / 60); 1454 $processing_time_seconds = $processing_time - $processing_time_minutes * 60; 1455 $str_processing_time = ""; 1456 if ($processing_time_minutes > 0) { 1457 $str_processing_time = $processing_time_minutes . " " . ($processing_time_minutes == 1 ? $this->lng->txt("minute") : $this->lng->txt("minutes")); 1458 } 1459 if ($processing_time_seconds > 0) { 1460 if (strlen($str_processing_time) > 0) { 1461 $str_processing_time .= " " . $this->lng->txt("and") . " "; 1462 } 1463 $str_processing_time .= $processing_time_seconds . " " . ($processing_time_seconds == 1 ? $this->lng->txt("second") : $this->lng->txt("seconds")); 1464 } 1465 $time_left = $starting_time + $processing_time - time(); 1466 $time_left_minutes = floor($time_left / 60); 1467 $time_left_seconds = $time_left - $time_left_minutes * 60; 1468 $str_time_left = ""; 1469 if ($time_left_minutes > 0) { 1470 $str_time_left = $time_left_minutes . " " . ($time_left_minutes == 1 ? $this->lng->txt("minute") : $this->lng->txt("minutes")); 1471 } 1472 if ($time_left < 300) { 1473 if ($time_left_seconds > 0) { 1474 if (strlen($str_time_left) > 0) { 1475 $str_time_left .= " " . $this->lng->txt("and") . " "; 1476 } 1477 $str_time_left .= $time_left_seconds . " " . ($time_left_seconds == 1 ? $this->lng->txt("second") : $this->lng->txt("seconds")); 1478 } 1479 } 1480 $date = getdate($starting_time); 1481 $formattedStartingTime = ilDatePresentation::formatDate(new ilDateTime($date, IL_CAL_FKT_GETDATE)); 1482 $datenow = getdate(); 1483 $this->tpl->setCurrentBlock("enableprocessingtime"); 1484 $this->tpl->setVariable( 1485 "USER_WORKING_TIME", 1486 sprintf( 1487 $this->lng->txt("tst_time_already_spent"), 1488 $formattedStartingTime, 1489 $str_processing_time 1490 ) 1491 ); 1492 $this->tpl->setVariable("USER_REMAINING_TIME", sprintf($this->lng->txt("tst_time_already_spent_left"), $str_time_left)); 1493 $this->tpl->parseCurrentBlock(); 1494 1495 // jQuery is required by tpl.workingtime.js 1496 require_once "./Services/jQuery/classes/class.iljQueryUtil.php"; 1497 iljQueryUtil::initjQuery(); 1498 $template = new ilTemplate("tpl.workingtime.js", true, true, 'Modules/Test'); 1499 $template->setVariable("STRING_MINUTE", $this->lng->txt("minute")); 1500 $template->setVariable("STRING_MINUTES", $this->lng->txt("minutes")); 1501 $template->setVariable("STRING_SECOND", $this->lng->txt("second")); 1502 $template->setVariable("STRING_SECONDS", $this->lng->txt("seconds")); 1503 $template->setVariable("STRING_TIMELEFT", $this->lng->txt("tst_time_already_spent_left")); 1504 $template->setVariable("AND", strtolower($this->lng->txt("and"))); 1505 $template->setVariable("YEAR", $date["year"]); 1506 $template->setVariable("MONTH", $date["mon"] - 1); 1507 $template->setVariable("DAY", $date["mday"]); 1508 $template->setVariable("HOUR", $date["hours"]); 1509 $template->setVariable("MINUTE", $date["minutes"]); 1510 $template->setVariable("SECOND", $date["seconds"]); 1511 if ($this->object->isEndingTimeEnabled()) { 1512 $date_time = new ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX); 1513 preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $date_time->get(IL_CAL_TIMESTAMP), $matches); 1514 $template->setVariable("ENDYEAR", $matches[1]); 1515 $template->setVariable("ENDMONTH", $matches[2] - 1); 1516 $template->setVariable("ENDDAY", $matches[3]); 1517 $template->setVariable("ENDHOUR", $matches[4]); 1518 $template->setVariable("ENDMINUTE", $matches[5]); 1519 $template->setVariable("ENDSECOND", $matches[6]); 1520 } 1521 $template->setVariable("YEARNOW", $datenow["year"]); 1522 $template->setVariable("MONTHNOW", $datenow["mon"] - 1); 1523 $template->setVariable("DAYNOW", $datenow["mday"]); 1524 $template->setVariable("HOURNOW", $datenow["hours"]); 1525 $template->setVariable("MINUTENOW", $datenow["minutes"]); 1526 $template->setVariable("SECONDNOW", $datenow["seconds"]); 1527 $template->setVariable("PTIME_M", $processing_time_minutes); 1528 $template->setVariable("PTIME_S", $processing_time_seconds); 1529 if ($this->ctrl->getCmd() == 'outQuestionSummary') { 1530 $template->setVariable("REDIRECT_URL", $this->ctrl->getFormAction($this, 'redirectAfterDashboard')); 1531 } else { 1532 $template->setVariable("REDIRECT_URL", ""); 1533 } 1534 $this->tpl->addOnLoadCode($template->get()); 1535 } 1536 1537 protected function showSideList($presentationMode, $currentSequenceElement) 1538 { 1539 global $DIC; 1540 $ilUser = $DIC['ilUser']; 1541 1542 $sideListActive = $ilUser->getPref('side_list_of_questions'); 1543 1544 if ($sideListActive) { 1545 $this->tpl->addCss( 1546 ilUtil::getStyleSheetLocation("output", "ta_split.css", "Modules/Test"), 1547 "screen" 1548 ); 1549 1550 $questionSummaryData = $this->service->getQuestionSummaryData($this->testSequence, false); 1551 1552 require_once 'Modules/Test/classes/class.ilTestQuestionSideListGUI.php'; 1553 $questionSideListGUI = new ilTestQuestionSideListGUI($this->ctrl, $this->lng); 1554 $questionSideListGUI->setTargetGUI($this); 1555 $questionSideListGUI->setQuestionSummaryData($questionSummaryData); 1556 $questionSideListGUI->setCurrentSequenceElement($currentSequenceElement); 1557 // fau: testNav - set side list presentation mode to "view" to allow navigation when question is in edit mode 1558 $questionSideListGUI->setCurrentPresentationMode(ilTestPlayerAbstractGUI::PRESENTATION_MODE_VIEW); 1559 $questionSideListGUI->setDisabled(false); 1560 // $questionSideListGUI->setCurrentPresentationMode($presentationMode); 1561 // $questionSideListGUI->setDisabled($presentationMode == self::PRESENTATION_MODE_EDIT); 1562 // fau. 1563 $this->tpl->setVariable('LIST_OF_QUESTIONS', $questionSideListGUI->getHTML()); 1564 } 1565 } 1566 1567 abstract protected function isQuestionSummaryFinishTestButtonRequired(); 1568 1569 /** 1570 * Output of a summary of all test questions for test participants 1571 */ 1572 public function outQuestionSummaryCmd($fullpage = true, $contextFinishTest = false, $obligationsInfo = false, $obligationsFilter = false) 1573 { 1574 if ($fullpage) { 1575 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_question_summary.html", "Modules/Test"); 1576 } 1577 1578 $obligationsFulfilled = \ilObjTest::allObligationsAnswered( 1579 $this->object->getId(), 1580 $this->testSession->getActiveId(), 1581 $this->testSession->getPass() 1582 ); 1583 1584 if ($obligationsInfo && $this->object->areObligationsEnabled() && !$obligationsFulfilled) { 1585 ilUtil::sendFailure($this->lng->txt('not_all_obligations_answered')); 1586 } 1587 1588 if ($this->object->getKioskMode() && $fullpage) { 1589 $head = $this->getKioskHead(); 1590 if (strlen($head)) { 1591 $this->tpl->setCurrentBlock("kiosk_options"); 1592 $this->tpl->setVariable("KIOSK_HEAD", $head); 1593 $this->tpl->parseCurrentBlock(); 1594 } 1595 } 1596 1597 1598 $active_id = $this->testSession->getActiveId(); 1599 $questionSummaryData = $this->service->getQuestionSummaryData($this->testSequence, $obligationsFilter); 1600 1601 $this->ctrl->setParameter($this, "sequence", $_GET["sequence"]); 1602 1603 if ($fullpage) { 1604 include_once "./Modules/Test/classes/tables/class.ilListOfQuestionsTableGUI.php"; 1605 $table_gui = new ilListOfQuestionsTableGUI($this, 'showQuestion'); 1606 1607 $table_gui->setShowPointsEnabled(!$this->object->getTitleOutput()); 1608 $table_gui->setShowMarkerEnabled($this->object->getShowMarker()); 1609 $table_gui->setObligationsNotAnswered(!$obligationsFulfilled); 1610 $table_gui->setShowObligationsEnabled($this->object->areObligationsEnabled()); 1611 $table_gui->setObligationsFilterEnabled($obligationsFilter); 1612 $table_gui->setFinishTestButtonEnabled($this->isQuestionSummaryFinishTestButtonRequired()); 1613 1614 $table_gui->init(); 1615 1616 $table_gui->setData($questionSummaryData); 1617 1618 $this->tpl->setVariable('TABLE_LIST_OF_QUESTIONS', $table_gui->getHTML()); 1619 1620 if ($this->object->getEnableProcessingTime()) { 1621 $this->outProcessingTime($active_id); 1622 } 1623 1624 if ($this->object->isShowExamIdInTestPassEnabled()) { 1625 $this->tpl->setCurrentBlock('exam_id_footer'); 1626 $this->tpl->setVariable('EXAM_ID_VAL', ilObjTest::lookupExamId( 1627 $this->testSession->getActiveId(), 1628 $this->testSession->getPass(), 1629 $this->object->getId() 1630 )); 1631 $this->tpl->setVariable('EXAM_ID_TXT', $this->lng->txt('exam_id')); 1632 $this->tpl->parseCurrentBlock(); 1633 } 1634 } 1635 } 1636 1637 public function outQuestionSummaryWithObligationsInfoCmd() 1638 { 1639 return $this->outQuestionSummaryCmd(true, true, true, false); 1640 } 1641 1642 public function outObligationsOnlySummaryCmd() 1643 { 1644 return $this->outQuestionSummaryCmd(true, true, true, true); 1645 } 1646 1647 public function showMaximumAllowedUsersReachedMessage() 1648 { 1649 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_max_allowed_users_reached.html", "Modules/Test"); 1650 $this->tpl->setCurrentBlock("adm_content"); 1651 $this->tpl->setVariable("MAX_ALLOWED_USERS_MESSAGE", sprintf($this->lng->txt("tst_max_allowed_users_message"), $this->object->getAllowedUsersTimeGap())); 1652 $this->tpl->setVariable("MAX_ALLOWED_USERS_HEADING", sprintf($this->lng->txt("tst_max_allowed_users_heading"), $this->object->getAllowedUsersTimeGap())); 1653 $this->tpl->setVariable("CMD_BACK_TO_INFOSCREEN", ilTestPlayerCommands::BACK_TO_INFO_SCREEN); 1654 $this->tpl->setVariable("TXT_BACK_TO_INFOSCREEN", $this->lng->txt("tst_results_back_introduction")); 1655 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this)); 1656 $this->tpl->parseCurrentBlock(); 1657 } 1658 1659 public function backFromFinishingCmd() 1660 { 1661 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 1662 } 1663 1664 /** 1665 * Creates an output of the solution of an answer compared to the correct solution 1666 * 1667 * @access public 1668 */ 1669 public function outCorrectSolution() 1670 { 1671 $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_tst_correct_solution.html", "Modules/Test"); 1672 1673 include_once("./Services/Style/Content/classes/class.ilObjStyleSheet.php"); 1674 $this->tpl->setCurrentBlock("ContentStyle"); 1675 $this->tpl->setVariable("LOCATION_CONTENT_STYLESHEET", ilObjStyleSheet::getContentStylePath(0)); 1676 $this->tpl->parseCurrentBlock(); 1677 1678 $this->tpl->setCurrentBlock("SyntaxStyle"); 1679 $this->tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", ilObjStyleSheet::getSyntaxStylePath()); 1680 $this->tpl->parseCurrentBlock(); 1681 1682 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print"); 1683 if ($this->object->getShowSolutionAnswersOnly()) { 1684 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print"); 1685 } 1686 1687 $this->tpl->setCurrentBlock("adm_content"); 1688 $solution = $this->getCorrectSolutionOutput($_GET["evaluation"], $_GET["active_id"], $_GET["pass"]); 1689 $this->tpl->setVariable("OUTPUT_SOLUTION", $solution); 1690 $this->tpl->setVariable("TEXT_BACK", $this->lng->txt("back")); 1691 $this->ctrl->saveParameter($this, "pass"); 1692 $this->ctrl->saveParameter($this, "active_id"); 1693 $this->tpl->setVariable("URL_BACK", $this->ctrl->getLinkTarget($this, "outUserResultsOverview")); 1694 $this->tpl->parseCurrentBlock(); 1695 } 1696 1697 /** 1698 * Creates an output of the list of answers for a test participant during the test 1699 * (only the actual pass will be shown) 1700 * 1701 * @param integer $active_id Active id of the participant 1702 * @param integer $pass Test pass of the participant 1703 * @param boolean $testnavigation Deceides wheather to show a navigation for tests or not 1704 * @access public 1705 */ 1706 public function showListOfAnswers($active_id, $pass = null, $top_data = "", $bottom_data = "") 1707 { 1708 global $DIC; 1709 $ilUser = $DIC['ilUser']; 1710 1711 $this->tpl->addBlockFile($this->getContentBlockName(), "adm_content", "tpl.il_as_tst_finish_list_of_answers.html", "Modules/Test"); 1712 1713 $result_array = &$this->object->getTestResult( 1714 $active_id, 1715 $pass, 1716 false, 1717 !$this->getObjectiveOrientedContainer()->isObjectiveOrientedPresentationRequired() 1718 ); 1719 1720 $counter = 1; 1721 // output of questions with solutions 1722 foreach ($result_array as $question_data) { 1723 $question = $question_data["qid"]; 1724 if (is_numeric($question)) { 1725 $this->tpl->setCurrentBlock("printview_question"); 1726 $question_gui = $this->object->createQuestionGUI("", $question); 1727 $template = new ilTemplate("tpl.il_as_qpl_question_printview.html", true, true, "Modules/TestQuestionPool"); 1728 $template->setVariable("COUNTER_QUESTION", $counter . ". "); 1729 $template->setVariable("QUESTION_TITLE", $question_gui->object->getTitle()); 1730 1731 $show_question_only = ($this->object->getShowSolutionAnswersOnly()) ? true : false; 1732 $result_output = $question_gui->getSolutionOutput($active_id, $pass, false, false, $show_question_only, $this->object->getShowSolutionFeedback()); 1733 $template->setVariable("SOLUTION_OUTPUT", $result_output); 1734 $this->tpl->setVariable("QUESTION_OUTPUT", $template->get()); 1735 $this->tpl->parseCurrentBlock(); 1736 $counter++; 1737 } 1738 } 1739 1740 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print.css", "Modules/Test"), "print"); 1741 if ($this->object->getShowSolutionAnswersOnly()) { 1742 $this->tpl->addCss(ilUtil::getStyleSheetLocation("output", "test_print_hide_content.css", "Modules/Test"), "print"); 1743 } 1744 if (strlen($top_data)) { 1745 $this->tpl->setCurrentBlock("top_data"); 1746 $this->tpl->setVariable("TOP_DATA", $top_data); 1747 $this->tpl->parseCurrentBlock(); 1748 } 1749 1750 if (strlen($bottom_data)) { 1751 $this->tpl->setCurrentBlock("bottom_data"); 1752 $this->tpl->setVariable("FORMACTION", $this->ctrl->getFormAction($this)); 1753 $this->tpl->setVariable("BOTTOM_DATA", $bottom_data); 1754 $this->tpl->parseCurrentBlock(); 1755 } 1756 1757 $this->tpl->setCurrentBlock("adm_content"); 1758 $this->tpl->setVariable("TXT_ANSWER_SHEET", $this->lng->txt("tst_list_of_answers")); 1759 $user_data = $this->getAdditionalUsrDataHtmlAndPopulateWindowTitle($this->testSession, $active_id, true); 1760 $signature = $this->getResultsSignature(); 1761 $this->tpl->setVariable("USER_DETAILS", $user_data); 1762 $this->tpl->setVariable("SIGNATURE", $signature); 1763 $this->tpl->setVariable("TITLE", $this->object->getTitle()); 1764 $this->tpl->setVariable("TXT_TEST_PROLOG", $this->lng->txt("tst_your_answers")); 1765 $invited_user = &$this->object->getInvitedUsers($ilUser->getId()); 1766 $pagetitle = $this->object->getTitle() . " - " . $this->lng->txt("clientip") . 1767 ": " . $invited_user[$ilUser->getId()]["clientip"] . " - " . 1768 $this->lng->txt("matriculation") . ": " . 1769 $invited_user[$ilUser->getId()]["matriculation"]; 1770 $this->tpl->setVariable("PAGETITLE", $pagetitle); 1771 $this->tpl->parseCurrentBlock(); 1772 } 1773 1774 /** 1775 * Returns the name of the current content block (depends on the kiosk mode setting) 1776 * 1777 * @return string The name of the content block 1778 */ 1779 public function getContentBlockName() 1780 { 1781 return "ADM_CONTENT"; 1782 1783 if ($this->object->getKioskMode()) { 1784 $this->tpl->setBodyClass("kiosk"); 1785 $this->tpl->hideFooter(); 1786 return "CONTENT"; 1787 } else { 1788 return "ADM_CONTENT"; 1789 } 1790 } 1791 1792 public function outUserResultsOverviewCmd() 1793 { 1794 $this->ctrl->redirectByClass( 1795 array('ilRepositoryGUI', 'ilObjTestGUI', 'ilTestEvaluationGUI'), 1796 "outUserResultsOverview" 1797 ); 1798 } 1799 1800 /** 1801 * Go to requested hint list 1802 */ 1803 protected function showRequestedHintListCmd() 1804 { 1805 // fau: testNav - handle intermediate submit for viewing requested hints 1806 $this->handleIntermediateSubmit(); 1807 // fau. 1808 1809 $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT); 1810 1811 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php'; 1812 $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_SHOW_LIST); 1813 } 1814 1815 /** 1816 * Go to hint request confirmation 1817 */ 1818 protected function confirmHintRequestCmd() 1819 { 1820 // fau: testNav - handle intermediate submit for confirming hint requests 1821 $this->handleIntermediateSubmit(); 1822 // fau. 1823 1824 $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_EDIT); 1825 1826 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintRequestGUI.php'; 1827 $this->ctrl->redirectByClass('ilAssQuestionHintRequestGUI', ilAssQuestionHintRequestGUI::CMD_CONFIRM_REQUEST); 1828 } 1829 1830 abstract protected function isFirstQuestionInSequence($sequenceElement); 1831 1832 abstract protected function isLastQuestionInSequence($sequenceElement); 1833 1834 1835 abstract protected function handleQuestionActionCmd(); 1836 1837 abstract protected function showInstantResponseCmd(); 1838 1839 abstract protected function nextQuestionCmd(); 1840 1841 abstract protected function previousQuestionCmd(); 1842 1843 protected function prepareSummaryPage() 1844 { 1845 $this->tpl->addBlockFile( 1846 $this->getContentBlockName(), 1847 'adm_content', 1848 'tpl.il_as_tst_question_summary.html', 1849 'Modules/Test' 1850 ); 1851 1852 if ($this->object->getKioskMode()) { 1853 $this->populateKioskHead(); 1854 } 1855 } 1856 1857 protected function initTestPageTemplate() 1858 { 1859 $this->tpl->addBlockFile( 1860 $this->getContentBlockName(), 1861 'adm_content', 1862 'tpl.il_as_tst_output.html', 1863 'Modules/Test' 1864 ); 1865 } 1866 1867 protected function populateKioskHead() 1868 { 1869 ilUtil::sendInfo(); // ??? 1870 1871 $head = $this->getKioskHead(); 1872 1873 if (strlen($head)) { 1874 $this->tpl->setCurrentBlock("kiosk_options"); 1875 $this->tpl->setVariable("KIOSK_HEAD", $head); 1876 $this->tpl->parseCurrentBlock(); 1877 } 1878 } 1879 1880 protected function handlePasswordProtectionRedirect() 1881 { 1882 if ($this->ctrl->getNextClass() == 'iltestpasswordprotectiongui') { 1883 return; 1884 } 1885 1886 if (!$this->passwordChecker->isPasswordProtectionPageRedirectRequired()) { 1887 return; 1888 } 1889 1890 $this->ctrl->setParameter($this, 'lock', $this->getLockParameter()); 1891 1892 $nextCommand = $this->ctrl->getCmdClass() . '::' . $this->ctrl->getCmd(); 1893 $this->ctrl->setParameterByClass('ilTestPasswordProtectionGUI', 'nextCommand', $nextCommand); 1894 $this->ctrl->redirectByClass('ilTestPasswordProtectionGUI', 'showPasswordForm'); 1895 } 1896 1897 protected function isParticipantsAnswerFixed($questionId) 1898 { 1899 if ($this->object->isInstantFeedbackAnswerFixationEnabled() && $this->testSequence->isQuestionChecked($questionId)) { 1900 return true; 1901 } 1902 1903 if ($this->object->isFollowupQuestionAnswerFixationEnabled() && $this->testSequence->isNextQuestionPresented($questionId)) { 1904 return true; 1905 } 1906 1907 return false; 1908 } 1909 1910 /** 1911 * @return string 1912 */ 1913 protected function getIntroductionPageButtonLabel() 1914 { 1915 return $this->lng->txt("save_introduction"); 1916 } 1917 1918 protected function initAssessmentSettings() 1919 { 1920 $this->assSettings = new ilSetting('assessment'); 1921 } 1922 1923 /** 1924 * @param ilTestSession $testSession 1925 */ 1926 protected function handleSkillTriggering(ilTestSession $testSession) 1927 { 1928 $questionList = $this->buildTestPassQuestionList(); 1929 $questionList->load(); 1930 1931 $testResults = $this->object->getTestResult($testSession->getActiveId(), $testSession->getPass(), true); 1932 1933 require_once 'Modules/Test/classes/class.ilTestSkillEvaluation.php'; 1934 $skillEvaluation = new ilTestSkillEvaluation($this->db, $this->object->getTestId(), $this->object->getRefId()); 1935 1936 $skillEvaluation->setUserId($testSession->getUserId()); 1937 $skillEvaluation->setActiveId($testSession->getActiveId()); 1938 $skillEvaluation->setPass($testSession->getPass()); 1939 1940 $skillEvaluation->setNumRequiredBookingsForSkillTriggering($this->assSettings->get( 1941 'ass_skl_trig_num_answ_barrier', 1942 ilObjAssessmentFolder::DEFAULT_SKL_TRIG_NUM_ANSWERS_BARRIER 1943 )); 1944 1945 1946 $skillEvaluation->init($questionList); 1947 $skillEvaluation->evaluate($testResults); 1948 1949 $skillEvaluation->handleSkillTriggering(); 1950 } 1951 1952 abstract protected function buildTestPassQuestionList(); 1953 1954 protected function showAnswerOptionalQuestionsConfirmation() 1955 { 1956 require_once 'Modules/Test/classes/confirmations/class.ilTestAnswerOptionalQuestionsConfirmationGUI.php'; 1957 $confirmation = new ilTestAnswerOptionalQuestionsConfirmationGUI($this->lng); 1958 1959 $confirmation->setFormAction($this->ctrl->getFormAction($this)); 1960 $confirmation->setCancelCmd('cancelAnswerOptionalQuestions'); 1961 $confirmation->setConfirmCmd('confirmAnswerOptionalQuestions'); 1962 1963 $confirmation->build($this->object->isFixedTest()); 1964 1965 $this->populateHelperGuiContent($confirmation); 1966 } 1967 1968 protected function confirmAnswerOptionalQuestionsCmd() 1969 { 1970 $this->testSequence->setAnsweringOptionalQuestionsConfirmed(true); 1971 $this->testSequence->saveToDb(); 1972 1973 $this->ctrl->setParameter($this, 'activecommand', 'gotoquestion'); 1974 $this->ctrl->redirect($this, 'redirectQuestion'); 1975 } 1976 1977 protected function cancelAnswerOptionalQuestionsCmd() 1978 { 1979 if ($this->object->getListOfQuestions()) { 1980 $this->ctrl->setParameter($this, 'activecommand', 'summary'); 1981 } else { 1982 $this->ctrl->setParameter($this, 'activecommand', 'previous'); 1983 } 1984 1985 $this->ctrl->redirect($this, 'redirectQuestion'); 1986 } 1987 1988 /** 1989 * @param $helperGui 1990 */ 1991 protected function populateHelperGuiContent($helperGui) 1992 { 1993 if ($this->object->getKioskMode()) { 1994 //$this->tpl->setBodyClass("kiosk"); 1995 $this->tpl->hideFooter(); 1996 $this->tpl->addBlockfile('CONTENT', 'adm_content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test"); 1997 $this->tpl->setContent($this->ctrl->getHTML($helperGui)); 1998 } else { 1999 $this->tpl->setVariable($this->getContentBlockName(), $this->ctrl->getHTML($helperGui)); 2000 } 2001 } 2002 2003 /** 2004 * @return bool $charSelectorAvailable 2005 */ 2006 protected function populateCharSelectorIfRequired() 2007 { 2008 global $DIC; 2009 $ilSetting = $DIC['ilSetting']; 2010 2011 if ($ilSetting->get('char_selector_availability') > 0) { 2012 require_once 'Services/UIComponent/CharSelector/classes/class.ilCharSelectorGUI.php'; 2013 $char_selector = ilCharSelectorGUI::_getCurrentGUI($this->object); 2014 if ($char_selector->getConfig()->getAvailability() == ilCharSelectorConfig::ENABLED) { 2015 $char_selector->addToPage(); 2016 $this->tpl->setCurrentBlock('char_selector'); 2017 $this->tpl->setVariable("CHAR_SELECTOR_TEMPLATE", $char_selector->getSelectorHtml()); 2018 $this->tpl->parseCurrentBlock(); 2019 2020 return true; 2021 } 2022 } 2023 2024 return false; 2025 } 2026 2027 protected function getTestNavigationToolbarGUI() 2028 { 2029 global $DIC; 2030 $ilUser = $DIC['ilUser']; 2031 2032 require_once 'Modules/Test/classes/class.ilTestNavigationToolbarGUI.php'; 2033 $navigationToolbarGUI = new ilTestNavigationToolbarGUI($this->ctrl, $this->lng, $this); 2034 2035 $navigationToolbarGUI->setSuspendTestButtonEnabled($this->object->getShowCancel()); 2036 $navigationToolbarGUI->setQuestionTreeButtonEnabled($this->object->getListOfQuestions()); 2037 $navigationToolbarGUI->setQuestionTreeVisible($ilUser->getPref('side_list_of_questions')); 2038 $navigationToolbarGUI->setQuestionListButtonEnabled($this->object->getListOfQuestions()); 2039 $navigationToolbarGUI->setFinishTestCommand($this->getFinishTestCommand()); 2040 2041 return $navigationToolbarGUI; 2042 } 2043 2044 protected function buildReadOnlyStateQuestionNavigationGUI($questionId) 2045 { 2046 require_once 'Modules/Test/classes/class.ilTestQuestionNavigationGUI.php'; 2047 $navigationGUI = new ilTestQuestionNavigationGUI($this->lng); 2048 2049 if (!$this->isParticipantsAnswerFixed($questionId)) { 2050 $navigationGUI->setEditSolutionCommand(ilTestPlayerCommands::EDIT_SOLUTION); 2051 } 2052 2053 if ($this->object->getShowMarker()) { 2054 include_once "./Modules/Test/classes/class.ilObjTest.php"; 2055 $solved_array = ilObjTest::_getSolvedQuestions($this->testSession->getActiveId(), $questionId); 2056 $solved = 0; 2057 2058 if (count($solved_array) > 0) { 2059 $solved = array_pop($solved_array); 2060 $solved = $solved["solved"]; 2061 } 2062 // fau: testNav - change question mark command to link target 2063 if ($solved == 1) { 2064 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::UNMARK_QUESTION)); 2065 $navigationGUI->setQuestionMarked(true); 2066 } else { 2067 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::MARK_QUESTION)); 2068 $navigationGUI->setQuestionMarked(false); 2069 } 2070 } 2071 // fau. 2072 2073 return $navigationGUI; 2074 } 2075 2076 protected function buildEditableStateQuestionNavigationGUI($questionId, $charSelectorAvailable) 2077 { 2078 require_once 'Modules/Test/classes/class.ilTestQuestionNavigationGUI.php'; 2079 $navigationGUI = new ilTestQuestionNavigationGUI($this->lng); 2080 2081 if ($this->object->isForceInstantFeedbackEnabled()) { 2082 $navigationGUI->setSubmitSolutionCommand(ilTestPlayerCommands::SUBMIT_SOLUTION); 2083 } else { 2084 // fau: testNav - use simple "submitSolution" button instead of "submitSolutionAndNext" 2085 $navigationGUI->setSubmitSolutionCommand(ilTestPlayerCommands::SUBMIT_SOLUTION); 2086 // fau. 2087 } 2088 2089 // fau: testNav - add a 'revert changes' link for editable question 2090 $navigationGUI->setRevertChangesLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::REVERT_CHANGES)); 2091 // fau. 2092 2093 2094 // feedback 2095 switch (1) { 2096 case $this->object->getSpecificAnswerFeedback(): 2097 case $this->object->getGenericAnswerFeedback(): 2098 case $this->object->getAnswerFeedbackPoints(): 2099 case $this->object->getInstantFeedbackSolution(): 2100 2101 $navigationGUI->setAnswerFreezingEnabled($this->object->isInstantFeedbackAnswerFixationEnabled()); 2102 2103 if ($this->object->isForceInstantFeedbackEnabled()) { 2104 $navigationGUI->setForceInstantResponseEnabled(true); 2105 $navigationGUI->setInstantFeedbackCommand(ilTestPlayerCommands::SUBMIT_SOLUTION); 2106 } else { 2107 $navigationGUI->setInstantFeedbackCommand(ilTestPlayerCommands::SHOW_INSTANT_RESPONSE); 2108 } 2109 } 2110 2111 // hints 2112 if ($this->object->isOfferingQuestionHintsEnabled()) { 2113 $activeId = $this->testSession->getActiveId(); 2114 $pass = $this->testSession->getPass(); 2115 2116 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php'; 2117 $questionHintTracking = new ilAssQuestionHintTracking($questionId, $activeId, $pass); 2118 2119 if ($questionHintTracking->requestsPossible()) { 2120 $navigationGUI->setRequestHintCommand(ilTestPlayerCommands::CONFIRM_HINT_REQUEST); 2121 } 2122 2123 if ($questionHintTracking->requestsExist()) { 2124 $navigationGUI->setShowHintsCommand(ilTestPlayerCommands::SHOW_REQUESTED_HINTS_LIST); 2125 } 2126 } 2127 2128 $navigationGUI->setCharSelectorEnabled($charSelectorAvailable); 2129 2130 if ($this->object->getShowMarker()) { 2131 include_once "./Modules/Test/classes/class.ilObjTest.php"; 2132 $solved_array = ilObjTest::_getSolvedQuestions($this->testSession->getActiveId(), $questionId); 2133 $solved = 0; 2134 2135 if (count($solved_array) > 0) { 2136 $solved = array_pop($solved_array); 2137 $solved = $solved["solved"]; 2138 } 2139 2140 // fau: testNav - change question mark command to link target 2141 if ($solved == 1) { 2142 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::UNMARK_QUESTION_SAVE)); 2143 $navigationGUI->setQuestionMarked(true); 2144 } else { 2145 $navigationGUI->setQuestionMarkLinkTarget($this->ctrl->getLinkTarget($this, ilTestPlayerCommands::MARK_QUESTION_SAVE)); 2146 $navigationGUI->setQuestionMarked(false); 2147 } 2148 } 2149 // fau. 2150 return $navigationGUI; 2151 } 2152 2153 /** 2154 * @return string 2155 */ 2156 protected function getFinishTestCommand() 2157 { 2158 if (!$this->object->getListOfQuestionsEnd()) { 2159 return 'finishTest'; 2160 } 2161 2162 if ($this->object->areObligationsEnabled()) { 2163 $allObligationsAnswered = ilObjTest::allObligationsAnswered( 2164 $this->testSession->getTestId(), 2165 $this->testSession->getActiveId(), 2166 $this->testSession->getPass() 2167 ); 2168 2169 if (!$allObligationsAnswered) { 2170 return 'outQuestionSummaryWithObligationsInfo'; 2171 } 2172 } 2173 2174 return 'outQuestionSummary'; 2175 } 2176 2177 // fau: testNav - populateIntermediateSolutionSaver is obsolete and can be deletd. 2178 // /** 2179 // * @param assQuestionGUI $questionGui 2180 // */ 2181 // protected function populateIntermediateSolutionSaver(assQuestionGUI $questionGui) 2182 // { 2183 // $this->tpl->addJavaScript(ilUtil::getJSLocation("autosave.js", "Modules/Test")); 2184// 2185 // $this->tpl->setVariable("AUTOSAVE_URL", $this->ctrl->getFormAction( 2186 // $this, ilTestPlayerCommands::AUTO_SAVE, "", true 2187 // )); 2188// 2189 // if( $questionGui->isAutosaveable() && $this->object->getAutosave() ) 2190 // { 2191 // $formAction = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AUTO_SAVE, '', false, false); 2192// 2193 // $this->tpl->touchBlock('autosave'); 2194 // $this->tpl->setVariable("AUTOSAVEFORMACTION", $formAction); 2195 // $this->tpl->setVariable("AUTOSAVEINTERVAL", $this->object->getAutosaveIval()); 2196 // } 2197 // } 2198 // fau. 2199 2200 // fau: testNav - new function populateInstantResponseModal() 2201 protected function populateInstantResponseModal(assQuestionGUI $questionGui, $navUrl) 2202 { 2203 $questionGui->setNavigationGUI(null); 2204 $questionGui->getQuestionHeaderBlockBuilder()->setQuestionAnswered(true); 2205 2206 $answerFeedbackEnabled = $this->object->getSpecificAnswerFeedback(); 2207 2208 $solutionoutput = $questionGui->getSolutionOutput( 2209 $this->testSession->getActiveId(), #active_id 2210 $this->testSession->getPass(), #pass 2211 false, #graphical_output 2212 false, #result_output 2213 true, #show_question_only 2214 $answerFeedbackEnabled, #show_feedback 2215 false, #show_correct_solution 2216 false, #show_manual_scoring 2217 true #show_question_text 2218 ); 2219 2220 $pageoutput = $questionGui->outQuestionPage( 2221 "", 2222 $this->isShowingPostponeStatusReguired($questionGui->object->getId()), 2223 $this->testSession->getActiveId(), 2224 $solutionoutput 2225 ); 2226 2227 $tpl = new ilTemplate('tpl.tst_player_response_modal.html', true, true, 'Modules/Test'); 2228 2229 // populate the instant response blocks in the 2230 $saved_tpl = $this->tpl; 2231 $this->tpl = $tpl; 2232 $this->populateInstantResponseBlocks($questionGui, true); 2233 $this->tpl = $saved_tpl; 2234 2235 $tpl->setVariable('QUESTION_OUTPUT', $pageoutput); 2236 2237 $button = ilLinkButton::getInstance(); 2238 $button->setId('tst_confirm_feedback'); 2239 $button->setUrl($navUrl); 2240 $button->setCaption('proceed'); 2241 $button->setPrimary(true); 2242 $tpl->setVariable('BUTTON', $button->render()); 2243 2244 2245 require_once('Services/UIComponent/Modal/classes/class.ilModalGUI.php'); 2246 $modal = ilModalGUI::getInstance(); 2247 $modal->setType(ilModalGUI::TYPE_LARGE); 2248 $modal->setId('tst_question_feedback_modal'); 2249 $modal->setHeading($this->lng->txt('tst_instant_feedback')); 2250 $modal->setBody($tpl->get()); 2251 2252 $this->tpl->addOnLoadCode("$('#tst_question_feedback_modal').modal('show');"); 2253 $this->tpl->setVariable('INSTANT_RESPONSE_MODAL', $modal->getHTML()); 2254 } 2255 // fau; 2256 2257 2258 /** 2259 * @param assQuestionGUI $questionGui 2260 */ 2261 protected function populateInstantResponseBlocks(assQuestionGUI $questionGui, $authorizedSolution) 2262 { 2263 $this->populateFeedbackBlockHeader( 2264 !$this->object->getSpecificAnswerFeedback() || !$questionGui->hasInlineFeedback() 2265 ); 2266 2267 // This controls if the solution should be shown. 2268 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Solutions" 2269 if ($this->object->getInstantFeedbackSolution()) { 2270 $show_question_inline_score = $this->determineInlineScoreDisplay(); 2271 2272 // Notation of the params prior to getting rid of this crap in favor of a class 2273 $solutionoutput = $questionGui->getSolutionOutput( 2274 $this->testSession->getActiveId(), #active_id 2275 null, #pass 2276 false, #graphical_output 2277 $show_question_inline_score, #result_output 2278 true, #show_question_only 2279 false, #show_feedback 2280 true, #show_correct_solution 2281 false, #show_manual_scoring 2282 false #show_question_text 2283 ); 2284 $solutionoutput = str_replace('<h1 class="ilc_page_title_PageTitle"></h1>', '', $solutionoutput); 2285 $this->populateSolutionBlock($solutionoutput); 2286 } 2287 2288 $reachedPoints = $questionGui->object->getAdjustedReachedPoints( 2289 $this->testSession->getActiveId(), 2290 null, 2291 $authorizedSolution 2292 ); 2293 2294 $maxPoints = $questionGui->object->getMaximumPoints(); 2295 2296 $solutionCorrect = ($reachedPoints == $maxPoints); 2297 2298 // This controls if the score should be shown. 2299 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Results (Only Points)" 2300 if ($this->object->getAnswerFeedbackPoints()) { 2301 $this->populateScoreBlock($reachedPoints, $maxPoints); 2302 } 2303 2304 // This controls if the generic feedback should be shown. 2305 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Solutions" 2306 if ($this->object->getGenericAnswerFeedback()) { 2307 $this->populateGenericFeedbackBlock($questionGui, $solutionCorrect); 2308 } 2309 2310 // This controls if the specific feedback should be shown. 2311 // It gets the parameter "Scoring and Results" -> "Instant Feedback" -> "Show Answer-Specific Feedback" 2312 if ($this->object->getSpecificAnswerFeedback()) { 2313 $this->populateSpecificFeedbackBlock($questionGui); 2314 } 2315 } 2316 2317 protected function populateFeedbackBlockHeader($withFocusAnchor) 2318 { 2319 if ($withFocusAnchor) { 2320 $this->tpl->setCurrentBlock('inst_resp_id'); 2321 $this->tpl->setVariable('INSTANT_RESPONSE_FOCUS_ID', 'focus'); 2322 $this->tpl->parseCurrentBlock(); 2323 } 2324 2325 $this->tpl->setCurrentBlock('instant_response_header'); 2326 $this->tpl->setVariable('INSTANT_RESPONSE_HEADER', $this->lng->txt('tst_feedback')); 2327 $this->tpl->parseCurrentBlock(); 2328 } 2329 2330 protected function getCurrentSequenceElement() 2331 { 2332 if ($this->getSequenceElementParameter()) { 2333 return $this->getSequenceElementParameter(); 2334 } 2335 2336 return $this->testSession->getLastSequence(); 2337 } 2338 2339 protected function resetSequenceElementParameter() 2340 { 2341 unset($_GET['sequence']); 2342 $this->ctrl->setParameter($this, 'sequence', null); 2343 } 2344 2345 protected function getSequenceElementParameter() 2346 { 2347 if (isset($_GET['sequence'])) { 2348 return $_GET['sequence']; 2349 } 2350 2351 return null; 2352 } 2353 2354 protected function getPresentationModeParameter() 2355 { 2356 if (isset($_GET['pmode'])) { 2357 return $_GET['pmode']; 2358 } 2359 2360 return null; 2361 } 2362 2363 protected function getInstantResponseParameter() 2364 { 2365 if (isset($_GET['instresp'])) { 2366 return $_GET['instresp']; 2367 } 2368 2369 return null; 2370 } 2371 2372 protected function getNextCommandParameter() 2373 { 2374 if (isset($_POST['nextcmd']) && strlen($_POST['nextcmd'])) { 2375 return $_POST['nextcmd']; 2376 } 2377 2378 return null; 2379 } 2380 2381 protected function getNextSequenceParameter() 2382 { 2383 if (isset($_POST['nextseq']) && is_numeric($_POST['nextseq'])) { 2384 return (int) $_POST['nextseq']; 2385 } 2386 2387 return null; 2388 } 2389 2390 // fau: testNav - get the navigation url set by a submit from ilTestPlayerNavigationControl.js 2391 protected function getNavigationUrlParameter() 2392 { 2393 if (isset($_POST['test_player_navigation_url'])) { 2394 return $_POST['test_player_navigation_url']; 2395 } 2396 return null; 2397 } 2398 // fau. 2399 2400 // fau: testNav - get set and check the 'answer_changed' url parameter 2401 /** 2402 * Get the 'answer changed' status from the current request 2403 * It may be set by ilTestPlayerNavigationControl.js or by a previousRequest 2404 * @return bool 2405 */ 2406 protected function getAnswerChangedParameter() 2407 { 2408 return !empty($_GET['test_answer_changed']); 2409 } 2410 2411 /** 2412 * Set the 'answer changed' url parameter for generated links 2413 * @param bool $changed 2414 */ 2415 protected function setAnswerChangedParameter($changed = true) 2416 { 2417 $this->ctrl->setParameter($this, 'test_answer_changed', $changed ? '1' : '0'); 2418 } 2419 2420 2421 /** 2422 * Check the 'answer changed' parameter when a question form is intermediately submitted 2423 * - save or delete the intermediate solution 2424 * - save the parameter for the next request 2425 */ 2426 protected function handleIntermediateSubmit() 2427 { 2428 if ($this->getAnswerChangedParameter()) { 2429 $this->saveQuestionSolution(false); 2430 } else { 2431 $this->removeIntermediateSolution(); 2432 } 2433 $this->setAnswerChangedParameter($this->getAnswerChangedParameter()); 2434 } 2435 // fau. 2436 2437 // fau: testNav - save the switch to prevent the navigation confirmation 2438 /** 2439 * Save the save the switch to prevent the navigation confirmation 2440 */ 2441 protected function saveNavigationPreventConfirmation() 2442 { 2443 if (!empty($_POST['save_on_navigation_prevent_confirmation'])) { 2444 $_SESSION['save_on_navigation_prevent_confirmation'] = true; 2445 } 2446 2447 if (!empty($_POST[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM])) { 2448 $_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM] = true; 2449 } 2450 } 2451 // fau. 2452 2453 /** 2454 * @var array[assQuestion] 2455 */ 2456 private $cachedQuestionGuis = array(); 2457 2458 /** 2459 * @param $questionId 2460 * @param $sequenceElement 2461 * @return object 2462 */ 2463 protected function getQuestionGuiInstance($questionId, $fromCache = true) 2464 { 2465 global $DIC; 2466 $tpl = $DIC['tpl']; 2467 2468 if (!$fromCache || !isset($this->cachedQuestionGuis[$questionId])) { 2469 $questionGui = $this->object->createQuestionGUI("", $questionId); 2470 $questionGui->setTargetGui($this); 2471 $questionGui->setPresentationContext(assQuestionGUI::PRESENTATION_CONTEXT_TEST); 2472 $questionGui->object->setObligationsToBeConsidered($this->object->areObligationsEnabled()); 2473 $questionGui->populateJavascriptFilesRequiredForWorkForm($tpl); 2474 $questionGui->object->setOutputType(OUTPUT_JAVASCRIPT); 2475 $questionGui->object->setShuffler($this->buildQuestionAnswerShuffler($questionId)); 2476 2477 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly 2478 $this->initTestQuestionConfig($questionGui->object); 2479 // hey. 2480 2481 $this->cachedQuestionGuis[$questionId] = $questionGui; 2482 } 2483 2484 return $this->cachedQuestionGuis[$questionId]; 2485 } 2486 2487 /** 2488 * @var array[assQuestion] 2489 */ 2490 private $cachedQuestionObjects = array(); 2491 2492 /** 2493 * @param $questionId 2494 * @return assQuestion 2495 */ 2496 protected function getQuestionInstance($questionId, $fromCache = true) 2497 { 2498 global $DIC; 2499 $ilDB = $DIC['ilDB']; 2500 $ilUser = $DIC['ilUser']; 2501 2502 if (!$fromCache || !isset($this->cachedQuestionObjects[$questionId])) { 2503 $questionOBJ = assQuestion::_instantiateQuestion($questionId); 2504 2505 $assSettings = new ilSetting('assessment'); 2506 require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php'; 2507 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB); 2508 $processLockerFactory->setQuestionId($questionOBJ->getId()); 2509 $processLockerFactory->setUserId($ilUser->getId()); 2510 include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php"); 2511 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging()); 2512 $questionOBJ->setProcessLocker($processLockerFactory->getLocker()); 2513 2514 $questionOBJ->setObligationsToBeConsidered($this->object->areObligationsEnabled()); 2515 $questionOBJ->setOutputType(OUTPUT_JAVASCRIPT); 2516 2517 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly 2518 $this->initTestQuestionConfig($questionOBJ); 2519 // hey. 2520 2521 $this->cachedQuestionObjects[$questionId] = $questionOBJ; 2522 } 2523 2524 return $this->cachedQuestionObjects[$questionId]; 2525 } 2526 2527 // hey: prevPassSolutions - determine solution pass index and configure gui accordingly 2528 protected function initTestQuestionConfig(assQuestion $questionOBJ) 2529 { 2530 $questionOBJ->getTestPresentationConfig()->setPreviousPassSolutionReuseAllowed( 2531 $this->object->isPreviousSolutionReuseEnabled($this->testSession->getActiveId()) 2532 ); 2533 } 2534 // hey. 2535 2536 /** 2537 * @param $questionId 2538 * @return ilArrayElementShuffler 2539 */ 2540 protected function buildQuestionAnswerShuffler($questionId) 2541 { 2542 require_once 'Services/Randomization/classes/class.ilArrayElementShuffler.php'; 2543 $shuffler = new ilArrayElementShuffler(); 2544 2545 $fixedSeed = $this->buildFixedShufflerSeed($questionId); 2546 $shuffler->setSeed($fixedSeed); 2547 2548 return $shuffler; 2549 } 2550 2551 /** 2552 * @param $sequence 2553 * @param $questionId 2554 * @param $ilLog 2555 */ 2556 protected function handleTearsAndAngerQuestionIsNull($questionId, $sequenceElement) 2557 { 2558 global $DIC; 2559 $ilLog = $DIC['ilLog']; 2560 2561 $ilLog->write( 2562 "INV SEQ:" 2563 . "active={$this->testSession->getActiveId()} " 2564 . "qId=$questionId seq=$sequenceElement " 2565 . serialize($this->testSequence) 2566 ); 2567 2568 $ilLog->logStack('INV SEQ'); 2569 2570 $this->ctrl->setParameter($this, 'sequence', $this->testSequence->getFirstSequence()); 2571 $this->ctrl->redirect($this, ilTestPlayerCommands::SHOW_QUESTION); 2572 } 2573 2574 /** 2575 * @param $contentHTML 2576 */ 2577 protected function populateMessageContent($contentHTML) 2578 { 2579 if ($this->object->getKioskMode()) { 2580 $this->tpl->addBlockfile($this->getContentBlockName(), 'content', "tpl.il_as_tst_kiosk_mode_content.html", "Modules/Test"); 2581 $this->tpl->setContent($contentHTML); 2582 } else { 2583 $this->tpl->setVariable($this->getContentBlockName(), $contentHTML); 2584 } 2585 } 2586 2587 protected function populateModals() 2588 { 2589 require_once 'Services/UIComponent/Button/classes/class.ilSubmitButton.php'; 2590 require_once 'Services/UIComponent/Button/classes/class.ilLinkButton.php'; 2591 require_once 'Services/UIComponent/Modal/classes/class.ilModalGUI.php'; 2592 2593 $this->populateDiscardSolutionModal(); 2594 // fau: testNav - populateNavWhenChangedModal instead of populateNavWhileEditModal 2595 $this->populateNavWhenChangedModal(); 2596 // fau. 2597 2598 if ($this->object->isFollowupQuestionAnswerFixationEnabled()) { 2599 $this->populateNextLocksChangedModal(); 2600 2601 $this->populateNextLocksUnchangedModal(); 2602 } 2603 } 2604 2605 protected function populateDiscardSolutionModal() 2606 { 2607 $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test'); 2608 2609 $tpl->setVariable('CONFIRMATION_TEXT', $this->lng->txt('discard_answer_confirmation')); 2610 2611 $button = ilSubmitButton::getInstance(); 2612 $button->setCommand(ilTestPlayerCommands::DISCARD_SOLUTION); 2613 $button->setCaption('discard_answer'); 2614 $tpl->setCurrentBlock('buttons'); 2615 $tpl->setVariable('BUTTON', $button->render()); 2616 $tpl->parseCurrentBlock(); 2617 2618 $button = ilLinkButton::getInstance(); 2619 $button->setId('tst_cancel_discard_button'); 2620 $button->setCaption('cancel'); 2621 $button->setPrimary(true); 2622 $tpl->setCurrentBlock('buttons'); 2623 $tpl->setVariable('BUTTON', $button->render()); 2624 $tpl->parseCurrentBlock(); 2625 2626 $modal = ilModalGUI::getInstance(); 2627 $modal->setId('tst_discard_solution_modal'); 2628 $modal->setHeading($this->lng->txt('discard_answer')); 2629 $modal->setBody($tpl->get()); 2630 2631 $this->tpl->setCurrentBlock('discard_solution_modal'); 2632 $this->tpl->setVariable('DISCARD_SOLUTION_MODAL', $modal->getHTML()); 2633 $this->tpl->parseCurrentBlock(); 2634 2635 // fau: testNav - the discard solution modal is now handled by ilTestPlayerNavigationControl.js 2636// $this->tpl->addJavaScript('Modules/Test/js/ilTestPlayerDiscardSolutionModal.js', true); 2637// fau. 2638 } 2639 2640 // fau: testNav - populateNavWhileEditModal is obsolete and can be deleted. 2641 // protected function populateNavWhileEditModal() 2642 // { 2643 // require_once 'Services/Form/classes/class.ilFormPropertyGUI.php'; 2644 // require_once 'Services/Form/classes/class.ilHiddenInputGUI.php'; 2645// 2646 // $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test'); 2647// 2648 // $tpl->setVariable('CONFIRMATION_TEXT', $this->lng->txt('tst_nav_while_edit_modal_text')); 2649// 2650 // $button = ilSubmitButton::getInstance(); 2651 // $button->setCommand(ilTestPlayerCommands::SUBMIT_SOLUTION); 2652 // $button->setCaption('tst_nav_while_edit_modal_save_btn'); 2653 // $button->setPrimary(true); 2654 // $tpl->setCurrentBlock('buttons'); 2655 // $tpl->setVariable('BUTTON', $button->render()); 2656 // $tpl->parseCurrentBlock(); 2657// 2658 // foreach(array('nextcmd', 'nextseq') as $hiddenPostVar) 2659 // { 2660 // $nextCmdInp = new ilHiddenInputGUI($hiddenPostVar); 2661 // $nextCmdInp->setValue(''); 2662 // $tpl->setCurrentBlock('hidden_inputs'); 2663 // $tpl->setVariable('HIDDEN_INPUT', $nextCmdInp->getToolbarHTML()); 2664 // $tpl->parseCurrentBlock(); 2665 // } 2666// 2667 // $button = ilLinkButton::getInstance(); 2668 // $this->ctrl->setParameter($this, 'pmode', self::PRESENTATION_MODE_VIEW); 2669 // $button->setId('nextCmdLink'); 2670 // $button->setUrl('#'); 2671 // $this->ctrl->setParameter($this, 'pmode', $this->getPresentationModeParameter()); 2672 // $button->setCaption('tst_nav_while_edit_modal_nosave_btn'); 2673 // $tpl->setCurrentBlock('buttons'); 2674 // $tpl->setVariable('BUTTON', $button->render()); 2675 // $tpl->parseCurrentBlock(); 2676// 2677 // $button = ilLinkButton::getInstance(); 2678 // $button->setId('tst_cancel_nav_while_edit_button'); 2679 // $button->setCaption('tst_nav_while_edit_modal_cancel_btn'); 2680 // $tpl->setCurrentBlock('buttons'); 2681 // $tpl->setVariable('BUTTON', $button->render()); 2682 // $tpl->parseCurrentBlock(); 2683// 2684 // $modal = ilModalGUI::getInstance(); 2685 // $modal->setId('tst_nav_while_edit_modal'); 2686 // $modal->setHeading($this->lng->txt('tst_nav_while_edit_modal_header')); 2687 // $modal->setBody($tpl->get()); 2688// 2689 // $this->tpl->setCurrentBlock('nav_while_edit_modal'); 2690 // $this->tpl->setVariable('NAV_WHILE_EDIT_MODAL', $modal->getHTML()); 2691 // $this->tpl->parseCurrentBlock(); 2692// 2693 // $this->tpl->addJavaScript('Modules/Test/js/ilTestPlayerNavWhileEditModal.js', true); 2694 // } 2695 // fau. 2696 2697 // fau: testNav - new function populateNavWhenChangedModal 2698 protected function populateNavWhenChangedModal() 2699 { 2700 return; // usibility fix: get rid of popup 2701 2702 if (!empty($_SESSION['save_on_navigation_prevent_confirmation'])) { 2703 return; 2704 } 2705 2706 $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test'); 2707 2708 if ($this->object->isInstantFeedbackAnswerFixationEnabled() && $this->object->isForceInstantFeedbackEnabled()) { 2709 $text = $this->lng->txt('save_on_navigation_locked_confirmation'); 2710 } else { 2711 $text = $this->lng->txt('save_on_navigation_confirmation'); 2712 } 2713 if ($this->object->isForceInstantFeedbackEnabled()) { 2714 $text .= " " . $this->lng->txt('save_on_navigation_forced_feedback_hint'); 2715 } 2716 $tpl->setVariable('CONFIRMATION_TEXT', $text); 2717 2718 2719 $button = ilLinkButton::getInstance(); 2720 $button->setId('tst_save_on_navigation_button'); 2721 $button->setUrl('#'); 2722 $button->setCaption('tst_save_and_proceed'); 2723 $button->setPrimary(true); 2724 $tpl->setCurrentBlock('buttons'); 2725 $tpl->setVariable('BUTTON', $button->render()); 2726 $tpl->parseCurrentBlock(); 2727 2728 $button = ilLinkButton::getInstance(); 2729 $button->setId('tst_cancel_on_navigation_button'); 2730 $button->setUrl('#'); 2731 $button->setCaption('cancel'); 2732 $button->setPrimary(false); 2733 $tpl->setCurrentBlock('buttons'); 2734 $tpl->setVariable('BUTTON', $button->render()); 2735 $tpl->parseCurrentBlock(); 2736 2737 $tpl->setCurrentBlock('checkbox'); 2738 $tpl->setVariable('CONFIRMATION_CHECKBOX_NAME', 'save_on_navigation_prevent_confirmation'); 2739 $tpl->setVariable('CONFIRMATION_CHECKBOX_LABEL', $this->lng->txt('tst_dont_show_msg_again_in_current_session')); 2740 $tpl->parseCurrentBlock(); 2741 2742 $modal = ilModalGUI::getInstance(); 2743 $modal->setId('tst_save_on_navigation_modal'); 2744 $modal->setHeading($this->lng->txt('save_on_navigation')); 2745 $modal->setBody($tpl->get()); 2746 2747 $this->tpl->setCurrentBlock('nav_while_edit_modal'); 2748 $this->tpl->setVariable('NAV_WHILE_EDIT_MODAL', $modal->getHTML()); 2749 $this->tpl->parseCurrentBlock(); 2750 } 2751 // fau. 2752 2753 protected function populateNextLocksUnchangedModal() 2754 { 2755 require_once 'Modules/Test/classes/class.ilTestPlayerConfirmationModal.php'; 2756 $modal = new ilTestPlayerConfirmationModal(); 2757 $modal->setModalId('tst_next_locks_unchanged_modal'); 2758 2759 $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_empty_answer_header')); 2760 $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_empty_answer_confirm')); 2761 2762 $button = $modal->buildModalButtonInstance('tst_nav_next_empty_answer_button'); 2763 $button->setCaption('tst_proceed'); 2764 $button->setPrimary(false); 2765 $modal->addButton($button); 2766 2767 $button = $modal->buildModalButtonInstance('tst_cancel_next_empty_answer_button'); 2768 $button->setCaption('cancel'); 2769 $button->setPrimary(true); 2770 $modal->addButton($button); 2771 2772 $this->tpl->setCurrentBlock('next_locks_unchanged_modal'); 2773 $this->tpl->setVariable('NEXT_LOCKS_UNCHANGED_MODAL', $modal->getHTML()); 2774 $this->tpl->parseCurrentBlock(); 2775 } 2776 2777 protected function populateNextLocksChangedModal() 2778 { 2779 if ($this->isFollowUpQuestionLocksConfirmationPrevented()) { 2780 return; 2781 } 2782 2783 require_once 'Modules/Test/classes/class.ilTestPlayerConfirmationModal.php'; 2784 $modal = new ilTestPlayerConfirmationModal(); 2785 $modal->setModalId('tst_next_locks_changed_modal'); 2786 2787 $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_current_answer_header')); 2788 $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_current_answer_confirm')); 2789 2790 $modal->setConfirmationCheckboxName(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM); 2791 $modal->setConfirmationCheckboxLabel($this->lng->txt('tst_dont_show_msg_again_in_current_session')); 2792 2793 $button = $modal->buildModalButtonInstance('tst_nav_next_changed_answer_button'); 2794 $button->setCaption('tst_save_and_proceed'); 2795 $button->setPrimary(true); 2796 $modal->addButton($button); 2797 2798 $button = $modal->buildModalButtonInstance('tst_cancel_next_changed_answer_button'); 2799 $button->setCaption('cancel'); 2800 $button->setPrimary(false); 2801 $modal->addButton($button); 2802 2803 $this->tpl->setCurrentBlock('next_locks_changed_modal'); 2804 $this->tpl->setVariable('NEXT_LOCKS_CHANGED_MODAL', $modal->getHTML()); 2805 $this->tpl->parseCurrentBlock(); 2806 } 2807 2808 const FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM = 'followup_qst_locks_prevent_confirmation'; 2809 2810 protected function setFollowUpQuestionLocksConfirmationPrevented() 2811 { 2812 $_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM] = true; 2813 } 2814 2815 protected function isFollowUpQuestionLocksConfirmationPrevented() 2816 { 2817 if (!isset($_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM])) { 2818 return false; 2819 } 2820 2821 return $_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM]; 2822 } 2823 2824 // fau: testNav - new function populateQuestionEditControl 2825 /** 2826 * Populate the navigation and saving control for editable questions 2827 * 2828 * @param assQuestionGUI $questionGUI 2829 */ 2830 protected function populateQuestionEditControl($questionGUI) 2831 { 2832 // configuration for ilTestPlayerQuestionEditControl.js 2833 $config = array(); 2834 2835 // set the initial state of the question 2836 $state = $questionGUI->object->lookupForExistingSolutions($this->testSession->getActiveId(), $this->testSession->getPass()); 2837 $config['isAnswered'] = $state['authorized']; 2838 $config['isAnswerChanged'] = $state['intermediate'] || $this->getAnswerChangedParameter(); 2839 2840 // set url to which the for should be submitted when the working time is over 2841 // don't use asynch url because the form is submitted directly 2842 // but use simple '&' because url is copied by javascript into the form action 2843 $config['saveOnTimeReachedUrl'] = str_replace('&', '&', $this->ctrl->getFormAction($this, ilTestPlayerCommands::AUTO_SAVE_ON_TIME_LIMIT)); 2844 2845 // enable the auto saving function 2846 // the autosave url is asynch because it will be used by an ajax request 2847 if ($questionGUI->isAutosaveable() && $this->object->getAutosave()) { 2848 $config['autosaveUrl'] = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::AUTO_SAVE, '', true); 2849 $config['autosaveInterval'] = $this->object->getAutosaveIval(); 2850 } else { 2851 $config['autosaveUrl'] = ''; 2852 $config['autosaveInterval'] = 0; 2853 } 2854 2855 /** @var ilTestQuestionConfig $questionConfig */ 2856 // hey: prevPassSolutions - refactored method identifiers 2857 $questionConfig = $questionGUI->object->getTestPresentationConfig(); 2858 // hey. 2859 2860 // Normal questions: changes are done in form fields an can be detected there 2861 $config['withFormChangeDetection'] = $questionConfig->isFormChangeDetectionEnabled(); 2862 2863 // Flash and Java questions: changes are directly sent to ilias and have to be polled from there 2864 $config['withBackgroundChangeDetection'] = $questionConfig->isBackgroundChangeDetectionEnabled(); 2865 $config['backgroundDetectorUrl'] = $this->ctrl->getLinkTarget($this, ilTestPlayerCommands::DETECT_CHANGES, '', true); 2866 2867 // Forced feedback will change the navigation saving command 2868 $config['forcedInstantFeedback'] = $this->object->isForceInstantFeedbackEnabled(); 2869 $config['nextQuestionLocks'] = $this->object->isFollowupQuestionAnswerFixationEnabled(); 2870 2871 $this->tpl->addJavascript('./Modules/Test/js/ilTestPlayerQuestionEditControl.js'); 2872 $this->tpl->addOnLoadCode('il.TestPlayerQuestionEditControl.init(' . json_encode($config) . ')'); 2873 } 2874 // fau. 2875 2876 protected function getQuestionsDefaultPresentationMode($isQuestionWorkedThrough) 2877 { 2878 // fau: testNav - always set default presentation mode to "edit" 2879 return self::PRESENTATION_MODE_EDIT; 2880 // fau. 2881 } 2882 2883 /** 2884 * @param $questionId 2885 * @return string 2886 */ 2887 protected function buildFixedShufflerSeed($questionId) 2888 { 2889 $fixedSeed = $questionId . $this->testSession->getActiveId() . $this->testSession->getPass(); 2890 2891 if (strlen($fixedSeed) < self::FIXED_SHUFFLER_SEED_MIN_LENGTH) { 2892 $fixedSeed *= ( 2893 10 * (self::FIXED_SHUFFLER_SEED_MIN_LENGTH - strlen($fixedSeed)) 2894 ); 2895 } 2896 2897 return $fixedSeed; 2898 } 2899 2900 protected function registerForcedFeedbackNavUrl($forcedFeedbackNavUrl) 2901 { 2902 if (!isset($_SESSION['forced_feedback_navigation_url'])) { 2903 $_SESSION['forced_feedback_navigation_url'] = array(); 2904 } 2905 2906 $_SESSION['forced_feedback_navigation_url'][$this->testSession->getActiveId()] = $forcedFeedbackNavUrl; 2907 } 2908 2909 protected function getRegisteredForcedFeedbackNavUrl() 2910 { 2911 if (!isset($_SESSION['forced_feedback_navigation_url'])) { 2912 return null; 2913 } 2914 2915 if (!isset($_SESSION['forced_feedback_navigation_url'][$this->testSession->getActiveId()])) { 2916 return null; 2917 } 2918 2919 return $_SESSION['forced_feedback_navigation_url'][$this->testSession->getActiveId()]; 2920 } 2921 2922 protected function isForcedFeedbackNavUrlRegistered() 2923 { 2924 return !empty($this->getRegisteredForcedFeedbackNavUrl()); 2925 } 2926 2927 protected function unregisterForcedFeedbackNavUrl() 2928 { 2929 if (isset($_SESSION['forced_feedback_navigation_url'][$this->testSession->getActiveId()])) { 2930 unset($_SESSION['forced_feedback_navigation_url'][$this->testSession->getActiveId()]); 2931 } 2932 } 2933} 2934