public function testIsDefault()
 {
     $itemSessionControl = new ItemSessionControl();
     $this->assertTrue($itemSessionControl->isDefault());
     $itemSessionControl->setMaxAttempts(0);
     $this->assertFalse($itemSessionControl->isDefault());
 }
 public function testAssigningScoresAndCorrectResponses()
 {
     $doc = new XmlDocument();
     $doc->load(self::samplesDir() . 'custom/items/template_processing.xml');
     $session = new AssessmentItemSession($doc->getDocumentComponent());
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setMaxAttempts(0);
     $session->setItemSessionControl($itemSessionControl);
     $session->beginItemSession();
     // Check that the templateProcessing was correctly processed.
     $this->assertEquals('ChoiceA', $session->getVariable('RESPONSE')->getCorrectResponse()->getValue());
     $this->assertEquals(1.0, $session['GOODSCORE']->getValue());
     $this->assertEquals(0.0, $session['WRONGSCORE']->getValue());
     // Check that it really works...
     // With a correct response.
     $session->beginAttempt();
     $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))));
     $session->endAttempt($responses);
     $this->assertEquals(1.0, $session['SCORE']->getValue());
     // With an incorrect response.
     $session->beginAttempt();
     $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceB'))));
     $session->endAttempt($responses);
     $this->assertEquals(0.0, $session['SCORE']->getValue());
 }
 public function testMarshallMaximal()
 {
     $assessmentSection1 = new ExtendedAssessmentSection('section1', 'My Section 1', true);
     $assessmentSection2 = new ExtendedAssessmentSection('section2', 'My Section 2', true);
     $preCondition = new PreCondition(new BaseValue(BaseType::BOOLEAN, true));
     $branching = new BranchRule(new BaseValue(BaseType::BOOLEAN, true), 'EXIT_TESTPART');
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setShowSolution(true);
     $timeLimits = new TimeLimits(null, new QtiDuration('PT1M40S'));
     $p = new P();
     $p->setContent(new InlineCollection(array(new TextRun('Prima!'))));
     $testFeedback = new TestFeedback('feedback1', 'show', new FlowStaticCollection(array($p)));
     $testFeedback->setTitle('hello!');
     $testFeedback->setAccess(TestFeedbackAccess::AT_END);
     $testFeedback->setShowHide(ShowHide::SHOW);
     $testFeedbackRef = new TestFeedbackRef('feedback1', 'show', TestFeedbackAccess::AT_END, ShowHide::SHOW, './TF01.xml');
     $assessmentSections = new AssessmentSectionCollection(array($assessmentSection1, $assessmentSection2));
     $preConditions = new PreConditionCollection(array($preCondition));
     $branchings = new BranchRuleCollection(array($branching));
     $testFeedbacks = new TestFeedbackCollection(array($testFeedback));
     $testFeedbackRefs = new TestFeedbackRefCollection(array($testFeedbackRef));
     $extendedTestPart = new ExtendedTestPart('part1', $assessmentSections);
     $extendedTestPart->setPreConditions($preConditions);
     $extendedTestPart->setBranchRules($branchings);
     $extendedTestPart->setItemSessionControl($itemSessionControl);
     $extendedTestPart->setTimeLimits($timeLimits);
     $extendedTestPart->setTestFeedbacks($testFeedbacks);
     $extendedTestPart->setTestFeedbackRefs($testFeedbackRefs);
     $factory = new CompactMarshallerFactory();
     $element = $factory->createMarshaller($extendedTestPart)->marshall($extendedTestPart);
     $dom = new DOMDocument('1.0', 'UTF-8');
     $element = $dom->importNode($element, true);
     $this->assertEquals('<testPart identifier="part1" navigationMode="linear" submissionMode="individual"><preCondition><baseValue baseType="boolean">true</baseValue></preCondition><branchRule target="EXIT_TESTPART"><baseValue baseType="boolean">true</baseValue></branchRule><itemSessionControl maxAttempts="1" showFeedback="false" allowReview="true" showSolution="true" allowComment="false" allowSkipping="true" validateResponses="false"/><timeLimits maxTime="100" allowLateSubmission="false"/><assessmentSection identifier="section1" required="false" fixed="false" title="My Section 1" visible="true" keepTogether="true"/><assessmentSection identifier="section2" required="false" fixed="false" title="My Section 2" visible="true" keepTogether="true"/><testFeedback access="atEnd" outcomeIdentifier="show" showHide="show" identifier="feedback1" title="hello!"><p>Prima!</p></testFeedback><testFeedbackRef identifier="feedback1" outcomeIdentifier="show" access="atEnd" showHide="show" href="./TF01.xml"/></testPart>', $dom->saveXML($element));
 }
 /**
  * @see \qtism\data\storage\xml\marshalling\Marshaller::unmarshall()
  */
 protected function unmarshall(DOMElement $element)
 {
     $object = new ItemSessionControl();
     if (($value = static::getDOMElementAttributeAs($element, 'maxAttempts', 'integer')) !== null) {
         $object->setMaxAttempts($value);
     }
     if (($value = static::getDOMElementAttributeAs($element, 'showFeedback', 'boolean')) !== null) {
         $object->setShowFeedback($value);
     }
     if (($value = static::getDOMElementAttributeAs($element, 'allowReview', 'boolean')) !== null) {
         $object->setAllowReview($value);
     }
     if (($value = static::getDOMElementAttributeAs($element, 'showSolution', 'boolean')) !== null) {
         $object->setShowSolution($value);
     }
     if (($value = static::getDOMElementAttributeAs($element, 'allowComment', 'boolean')) !== null) {
         $object->setAllowComment($value);
     }
     if (($value = static::getDOMElementAttributeAs($element, 'allowSkipping', 'boolean')) !== null) {
         $object->setAllowSkipping($value);
     }
     if (($value = static::getDOMElementAttributeAs($element, 'validateResponses', 'boolean')) !== null) {
         $object->setValidateResponses($value);
     }
     return $object;
 }
 public function testMarshall()
 {
     $component = new ItemSessionControl();
     $component->setAllowComment(true);
     $component->setMaxAttempts(2);
     $component->setValidateResponses(false);
     $marshaller = $this->getMarshallerFactory()->createMarshaller($component);
     $element = $marshaller->marshall($component);
     $this->assertInstanceOf('\\DOMElement', $element);
     $this->assertEquals('itemSessionControl', $element->nodeName);
     $this->assertEquals('true', $element->getAttribute('allowComment'));
     $this->assertEquals('2', $element->getAttribute('maxAttempts'));
     $this->assertEquals('false', $element->getAttribute('validateResponses'));
     $this->assertEquals('true', $element->getAttribute('allowReview'));
     $this->assertEquals('false', $element->getAttribute('showSolution'));
     $this->assertEquals('true', $element->getAttribute('allowSkipping'));
 }
 public function testMarshallNotRecursive()
 {
     $identifier = 'myAssessmentSection';
     $title = 'A non Recursive Assessment Section';
     $visible = true;
     $keepTogether = false;
     // preConditions
     $preConditions = new PreConditionCollection();
     $preConditions[] = new PreCondition(new BaseValue(BaseType::BOOLEAN, true));
     // branchRules
     $branchRules = new BranchRuleCollection();
     $branchRules[] = new BranchRule(new BaseValue(BaseType::BOOLEAN, false), 'EXIT_TEST');
     // itemSessionControl
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setAllowReview(true);
     // sectionParts
     $sectionParts = new SectionPartCollection();
     $sectionParts[] = new AssessmentItemRef('Q01', './questions/Q01.xml');
     $sectionParts[] = new AssessmentItemRef('Q02', './questions/Q02.xml');
     $sectionParts[] = new AssessmentSectionRef('S01', './sections/S01.xml');
     $component = new AssessmentSection($identifier, $title, $visible);
     $component->setKeepTogether($keepTogether);
     $component->setPreConditions($preConditions);
     $component->setBranchRules($branchRules);
     $component->setItemSessionControl($itemSessionControl);
     $component->setSectionParts($sectionParts);
     $marshaller = $this->getMarshallerFactory('2.1.0')->createMarshaller($component);
     $element = $marshaller->marshall($component);
     $this->assertInstanceOf('\\DOMElement', $element);
     $this->assertEquals($identifier, $element->getAttribute('identifier'));
     $this->assertEquals($title, $element->getAttribute('title'));
     $this->assertEquals('true', $element->getAttribute('visible'));
     $this->assertEquals('false', $element->getAttribute('keepTogether'));
     $this->assertEquals(1, $element->getElementsByTagName('preCondition')->length);
     $this->assertEquals(1, $element->getElementsByTagName('preCondition')->item(0)->getElementsByTagName('baseValue')->length);
     $this->assertEquals(1, $element->getElementsByTagName('branchRule')->length);
     $this->assertEquals(1, $element->getElementsByTagName('branchRule')->item(0)->getElementsByTagName('baseValue')->length);
     $this->assertEquals(1, $element->getElementsByTagName('itemSessionControl')->length);
     $this->assertEquals('true', $element->getElementsByTagName('itemSessionControl')->item(0)->getAttribute('allowReview'));
     $this->assertEquals(2, $element->getElementsByTagName('assessmentItemRef')->length);
     $this->assertEquals('Q02', $element->getElementsByTagName('assessmentItemRef')->item(1)->getAttribute('identifier'));
     $this->assertEquals(1, $element->getElementsByTagName('assessmentSectionRef')->length);
     $this->assertEquals('S01', $element->getElementsByTagName('assessmentSectionRef')->item(0)->getAttribute('identifier'));
 }
 public function testOnlyScalarPropertiesConstructorAndProperties()
 {
     $component = new ItemSessionControl();
     $ctx = $this->createMarshallingContext();
     $scalarMarshaller = new PhpScalarMarshaller($ctx, $component->getMaxAttempts());
     $scalarMarshaller->marshall();
     $scalarMarshaller->setToMarshall($component->mustShowFeedback());
     $scalarMarshaller->marshall();
     $scalarMarshaller->setToMarshall($component->doesAllowReview());
     $scalarMarshaller->marshall();
     $scalarMarshaller->setToMarshall($component->mustShowSolution());
     $scalarMarshaller->marshall();
     $scalarMarshaller->setToMarshall($component->doesAllowComment());
     $scalarMarshaller->marshall();
     $scalarMarshaller->setToMarshall($component->mustValidateResponses());
     $scalarMarshaller->marshall();
     $scalarMarshaller->setToMarshall($component->doesAllowSkipping());
     $scalarMarshaller->marshall();
     $componentMarshaller = new PhpQtiComponentMarshaller($ctx, $component);
     $componentMarshaller->marshall();
     $expected = "\$integer_0 = 1;\n";
     $expected .= "\$boolean_0 = false;\n";
     $expected .= "\$boolean_1 = true;\n";
     $expected .= "\$boolean_2 = false;\n";
     $expected .= "\$boolean_3 = false;\n";
     $expected .= "\$boolean_4 = false;\n";
     $expected .= "\$boolean_5 = true;\n";
     $expected .= "\$itemsessioncontrol_0 = new qtism\\data\\ItemSessionControl();\n";
     $expected .= "\$itemsessioncontrol_0->setMaxAttempts(\$integer_0);\n";
     $expected .= "\$itemsessioncontrol_0->setShowFeedback(\$boolean_0);\n";
     $expected .= "\$itemsessioncontrol_0->setAllowReview(\$boolean_1);\n";
     $expected .= "\$itemsessioncontrol_0->setShowSolution(\$boolean_2);\n";
     $expected .= "\$itemsessioncontrol_0->setAllowComment(\$boolean_3);\n";
     $expected .= "\$itemsessioncontrol_0->setValidateResponses(\$boolean_4);\n";
     $expected .= "\$itemsessioncontrol_0->setAllowSkipping(\$boolean_5);\n";
     $this->assertEquals($expected, $this->getStream()->getBinary());
 }
 public function testTemplateVariableDefault()
 {
     // This test aims at testing whether template variables
     // are correctly instantiated as part of the item session and
     // they can be used in response processing.
     $doc = new XmlDocument('2.1.0');
     $doc->load(self::samplesDir() . 'custom/items/template_declaration_default.xml');
     $itemSession = new AssessmentItemSession($doc->getDocumentComponent());
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setMaxAttempts(0);
     $itemSession->setItemSessionControl($itemSessionControl);
     $itemSession->beginItemSession();
     $this->assertTrue($itemSession['WRONGSCORE']->equals(new Float(0.0)));
     $this->assertTrue($itemSession['GOODSCORE']->equals(new Float(1.0)));
     // 1st attempt to get 'GOODSCORE' as 'SCORE'.
     $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new Identifier('ChoiceA'))));
     $itemSession->beginAttempt();
     $itemSession->endAttempt($responses);
     $this->assertTrue($itemSession['SCORE']->equals($itemSession['GOODSCORE']));
     // 2nd attempt to get 'WRONGSCORE' as 'SCORE'.
     $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new Identifier('ChoiceB'))));
     $itemSession->beginAttempt();
     $itemSession->endAttempt($responses);
     $this->assertTrue($itemSession['SCORE']->equals($itemSession['WRONGSCORE']));
 }
예제 #9
0
 /**
  * begin an attempt for this item session. The value of the built-in outcome variable 'completionStatus' is set to the 'unknown' value at
  * the beginning of the very first attempt on this session.
  *
  * * If the attempt to begin is the first one of the session, response variables are applied their default value.
  * * If the current submissionMode of the session is SIMULTANEOUS, only one call to beginAttempt() is allowed, otherwise an exception will be thrown.
  *
  * @throws \qtism\runtime\tests\AssessmentItemSessionException If the maximum number of attempts or the maximum time limit in force is reached.
  * @see http://www.imsglobal.org/question/qtiv2p1/imsqti_infov2p1.html#section10055 The IMS QTI 2.1 Item Session Lifecycle.
  */
 public function beginAttempt()
 {
     $maxAttempts = $this->itemSessionControl->getMaxAttempts();
     $numAttempts = $this['numAttempts']->getValue();
     $submissionMode = $this->getSubmissionMode();
     /* If the current submission mode is SIMULTANEOUS, only 1 attempt is allowed per item.
      *
      * From IMS QTI:
      *
      * In simultaneous mode, response processing cannot take place until the testPart is
      * complete so each item session passes between the interacting and suspended states only.
      * By definition the candidate can take one and only one attempt at each item and feedback
      * cannot be seen during the test. Whether or not the candidate can return to review
      * their responses and/or any item-level feedback after the test, is outside the scope
      * of this specification. Simultaneous mode is typical of paper-based tests.
      *
      */
     if ($submissionMode === SubmissionMode::SIMULTANEOUS) {
         $maxAttempts = 1;
     }
     // Check if we can perform a new attempt.
     if ($this->getState() === AssessmentItemSessionState::CLOSED) {
         if ($submissionMode === SubmissionMode::SIMULTANEOUS && $numAttempts > 0) {
             $identifier = $this->getAssessmentItem()->getIdentifier();
             $msg = "A new attempt for item '{$identifier}' is not allowed. The submissionMode is simultaneous and the only accepted attempt is already begun.";
             throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::ATTEMPTS_OVERFLOW);
         } elseif ($submissionMode === SubmissionMode::INDIVIDUAL && $maxAttempts !== 0 && $numAttempts >= $maxAttempts) {
             $identifier = $this->getAssessmentItem()->getIdentifier();
             $msg = "A new attempt for item '{$identifier}' is not allowed. The maximum number of attempts ({$maxAttempts}) is reached.";
             throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::ATTEMPTS_OVERFLOW);
         } elseif ($this->isMaxTimeReached() === true) {
             $identifier = $this->getAssessmentItem()->getIdentifier();
             $msg = "A new attempt for item '{$identifier}' is not allowed. The maximum time limit in force is reached.";
             throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::DURATION_OVERFLOW);
         } elseif ($this->getAssessmentItem()->isAdaptive() === true && $this['completionStatus']->getValue() === self::COMPLETION_STATUS_COMPLETED) {
             $identifier = $this->getAssessmentItem()->getIdentifier();
             $msg = "A new attempt for item '{$identifier}' is not allowed. It is adaptive and its completion status is 'completed'.";
             throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::ATTEMPTS_OVERFLOW);
         }
     }
     // Response variables' default values are set at the beginning of the first attempt.
     if ($numAttempts === 0) {
         $data =& $this->getDataPlaceHolder();
         // At the start of the first attempt, the completionStatus goes to 'unknown' and response
         // variables get their default values if any.
         foreach (array_keys($data) as $k) {
             if ($data[$k] instanceof ResponseVariable) {
                 $data[$k]->applyDefaultValue();
             }
         }
         $this['duration'] = new QtiDuration('PT0S');
         $this['numAttempts'] = new QtiInteger(0);
         // At the start of the first attempt, the completionStatus goes
         // to 'unknown'.
         $this['completionStatus']->setValue(self::COMPLETION_STATUS_UNKNOWN);
     }
     // For any attempt, the variables related to endAttemptInteractions are reset to false.
     foreach ($this->getAssessmentItem()->getEndAttemptIdentifiers() as $endAttemptIdentifier) {
         $this[$endAttemptIdentifier] = new QtiBoolean(false);
     }
     // Increment the built-in variable 'numAttempts' by one.
     $this['numAttempts']->setValue($numAttempts + 1);
     // The session get the INTERACTING state.
     $this->setState(AssessmentItemSessionState::INTERACTING);
     $this->setAttempting(true);
 }
 public function testEvolutionBasicMultipleAttempts()
 {
     $count = 5;
     $attempts = array(new Identifier('ChoiceA'), new Identifier('ChoiceB'), new Identifier('ChoiceC'), new Identifier('ChoiceD'), new Identifier('ChoiceE'));
     $expected = array(new Float(0.0), new Float(1.0), new Float(0.0), new Float(0.0), new Float(0.0));
     $itemSession = self::instantiateBasicAssessmentItemSession();
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setMaxAttempts($count);
     $itemSession->setItemSessionControl($itemSessionControl);
     $itemSession->beginItemSession();
     for ($i = 0; $i < $count; $i++) {
         // Here, manual set up of responses.
         $this->assertTrue($itemSession->isAttemptable());
         $itemSession->beginAttempt();
         // simulate some time... 1 second to answer the item.
         sleep(1);
         $itemSession['RESPONSE'] = $attempts[$i];
         $itemSession->endAttempt();
         $this->assertInstanceOf('qtism\\common\\datatypes\\Float', $itemSession['SCORE']);
         $this->assertTrue($expected[$i]->equals($itemSession['SCORE']));
         $this->assertEquals($i + 1, $itemSession['numAttempts']->getValue());
         // 1 more second before the next attempt.
         // we are here in suspended mode so it will not be
         // added to the duration.
         sleep(1);
     }
     // The total duration shold have taken 5 seconds, the rest of the time was in SUSPENDED state.
     $this->assertEquals(5, $itemSession['duration']->getSeconds(true));
     // one more and we get an expection... :)
     try {
         $this->assertFalse($itemSession->isAttemptable());
         $itemSession->beginAttempt();
         $this->assertTrue(false);
     } catch (AssessmentItemSessionException $e) {
         $this->assertEquals(AssessmentItemSessionException::STATE_VIOLATION, $e->getCode());
     }
 }
 public function testEvolutionBasicMultipleAttempts()
 {
     $count = 5;
     $attempts = array(new QtiIdentifier('ChoiceA'), new QtiIdentifier('ChoiceB'), new QtiIdentifier('ChoiceC'), new QtiIdentifier('ChoiceD'), new QtiIdentifier('ChoiceE'));
     $expected = array(new QtiFloat(0.0), new QtiFloat(1.0), new QtiFloat(0.0), new QtiFloat(0.0), new QtiFloat(0.0));
     $itemSession = self::instantiateBasicAssessmentItemSession();
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setMaxAttempts($count);
     $itemSession->setItemSessionControl($itemSessionControl);
     $itemSession->setTime(self::createDate('2014-07-14 13:00:00'));
     $itemSession->beginItemSession();
     for ($i = 0; $i < $count; $i++) {
         // Here, manual set up of responses.
         $this->assertTrue($itemSession->isAttemptable());
         $itemSession->beginAttempt();
         // simulate some time... 1 second to answer the item.
         $t = $i + 1;
         $itemSession->setTime(self::createDate("2014-07-14 13:00:0{$t}"));
         $itemSession['RESPONSE'] = $attempts[$i];
         $itemSession->endAttempt();
         $this->assertInstanceOf('qtism\\common\\datatypes\\QtiFloat', $itemSession['SCORE']);
         $this->assertTrue($expected[$i]->equals($itemSession['SCORE']));
         $this->assertEquals($t, $itemSession['numAttempts']->getValue());
     }
     // The total duration should have taken 5 seconds.
     $this->assertEquals(5, $itemSession['duration']->getSeconds(true));
     // one more and we get an exception... :)
     try {
         $this->assertFalse($itemSession->isAttemptable());
         $itemSession->beginAttempt();
         $this->assertTrue(false);
     } catch (AssessmentItemSessionException $e) {
         $this->assertEquals(AssessmentItemSessionException::ATTEMPTS_OVERFLOW, $e->getCode());
     }
 }
 /**
  * End the attempt by providing responses or by another action. If $responses
  * is provided, the values found into it will be merged to the current state
  * before ResponseProcessing is executed.
  * 
  * * If the item is adaptive and the completionStatus is indicated to be 'completed', the item session ends.
  * * If the item is non-adaptive, and the number of attempts is exceeded, the item session ends and the completionStatus is set to 'completed'.
  * * Otherwise, the item session goes to the SUSPENDED state, waiting for a next attempt.
  * 
  * @param State $responses (optional) A State composed by the candidate's responses to the item.
  * @param boolean $responseProcessing (optional) Whether to execute the responseProcessing or not.
  * @param boolean $allowLateSubmission If set to true, maximum time limits will not be taken into account, even if the a maximum time limit is in force.
  * @throws AssessmentItemSessionException
  */
 public function endAttempt(State $responses = null, $responseProcessing = true, $allowLateSubmission = false)
 {
     // End of attempt, go in SUSPEND state.
     $this->suspend();
     // Flag to indicate if time is exceed or not.
     $maxTimeExceeded = false;
     // Is timeLimits in force.
     if ($this->hasTimeLimits() === true) {
         // As per QTI 2.1 Spec, Minimum times are only applicable to assessmentSections and
         // assessmentItems only when linear navigation mode is in effect.
         if ($this->isNavigationLinear() === true && $this->timeLimits->hasMinTime() === true) {
             if ($this->mustConsiderMinTime() === true && $this['duration']->getSeconds(true) <= $this->timeLimits->getMinTime()->getSeconds(true)) {
                 // An exception is thrown to prevent the numAttempts to be incremented.
                 // Suspend and wait for a next attempt.
                 $this->suspend();
                 $msg = "The minimal duration is not yet reached.";
                 throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::DURATION_UNDERFLOW);
             }
         }
         // Check if the maxTime constraint is respected.
         // If late submission is allowed but time exceeded, the item session will be considered 'completed'.
         // Otherwise, if late submission is not allowed but time exceeded, the session goes to 'incomplete'.
         if ($this->isMaxTimeReached() === true) {
             $maxTimeExceeded = true;
             if ($this->timeLimits->doesAllowLateSubmission() === false && $allowLateSubmission === false) {
                 $this['completionStatus']->setValue(self::COMPLETION_STATUS_INCOMPLETE);
                 $msg = "The maximal duration is exceeded.";
                 $this->endItemSession();
                 throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::DURATION_OVERFLOW);
             } else {
                 $this['completionStatus']->setValue(self::COMPLETION_STATUS_COMPLETED);
                 $this->endItemSession();
             }
         }
     }
     // As per specs, when validateResponses is turned on (true) then the candidates are not
     // allowed to submit the item until they have provided valid responses for all
     // interactions. When turned off (false) invalid responses may be accepted by the
     // system.
     if ($this->submissionMode === SubmissionMode::INDIVIDUAL && $this->itemSessionControl->mustValidateResponses() === true) {
         // Use the correct expression to control if the responses
         // are correct.
         foreach ($responses as $response) {
             // @todo Reconsider the 'valid' concept.
             $correctExpression = new Correct($response->getIdentifier());
             $correctProcessor = new CorrectProcessor($correctExpression);
             $correctProcessor->setState($responses);
             try {
                 if ($correctProcessor->process() !== true) {
                     $responseIdentifier = $response->getIdentifier();
                     $msg = "The current itemSessionControl.validateResponses attribute is set to true but ";
                     $msg .= "response '{$responseIdentifier}' is incorrect.";
                     throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::INVALID_RESPONSE);
                 }
             } catch (ExpressionProcessingException $e) {
                 $responseIdentifier = $response->getIdentifier();
                 $msg = "The current itemSessionControl.validResponses attribute is set to true but an error ";
                 $msg .= "occured while trying to detect if response '{$responseIdentifier}' was correct.";
                 throw new AssessmentItemSessionException($msg, $this, AssessmentItemSessionException::RUNTIME_ERROR, $e);
             }
         }
     }
     // Apply the responses (if provided) to the current state and deal with the responseProcessing.
     if ($responses !== null) {
         foreach ($responses as $identifier => $value) {
             $this[$identifier] = $value->getValue();
         }
     }
     // Apply response processing.
     // As per QTI 2.1 specs, For Non-adaptive Items, the values of the outcome variables are reset to their
     // default values prior to each invocation of responseProcessing. For Adaptive Items the outcome variables
     // retain the values that were assigned to them during the previous invocation of response processing.
     // For more information, see Response Processing.
     //
     // The responseProcessing can be skipped by given a false value to $responseProcessing. Why?
     // Because when the SubmissionMode is SubmissionMode::SIMULTANEOUS, the responseProcessing must
     // deffered to the end of the current testPart.
     if ($responseProcessing === true) {
         if ($this->assessmentItem->isAdaptive() === false) {
             $this->resetOutcomeVariables();
         }
         $responseProcessing = $this->assessmentItem->getResponseProcessing();
         // Some items (especially to collect information) have no response processing!
         if ($responseProcessing !== null && ($responseProcessing->hasTemplate() === true || $responseProcessing->hasTemplateLocation() === true || count($responseProcessing->getResponseRules()) > 0)) {
             $engine = $this->createResponseProcessingEngine($responseProcessing);
             $engine->process();
         }
     }
     $maxAttempts = $this->itemSessionControl->getMaxAttempts();
     if ($this->submissionMode === SubmissionMode::SIMULTANEOUS) {
         $maxAttempts = 1;
     }
     // -- Adaptive or non-adaptive item, maximum time limit reached but late submission allowed.
     if ($maxTimeExceeded === true) {
         $this->endItemSession();
         $this['completionStatus']->setValue(self::COMPLETION_STATUS_COMPLETED);
     } else {
         if ($this->assessmentItem->isAdaptive() === true && $this->submissionMode === SubmissionMode::INDIVIDUAL && $this['completionStatus']->getValue() === self::COMPLETION_STATUS_COMPLETED) {
             $this->endItemSession();
         } else {
             if ($this->assessmentItem->isAdaptive() === false && $this['numAttempts']->getValue() >= $maxAttempts) {
                 // Close only if $maxAttempts !== 0 because 0 means no limit!
                 if ($maxAttempts !== 0) {
                     $this->endItemSession();
                 }
                 // Even if there is no limit of attempts, we consider the item completed.
                 $this['completionStatus']->setValue(self::COMPLETION_STATUS_COMPLETED);
             } else {
                 if ($this->assessmentItem->isAdaptive() === false && $this['numAttempts']->getValue() < $maxAttempts) {
                     $this['completionStatus']->setValue(self::COMPLETION_STATUS_COMPLETED);
                 }
             }
         }
     }
     // else...
     // Wait for the next attempt.
     $this->attempting = false;
 }
 public function testValidResponsesInForceValid()
 {
     $itemSession = self::instantiateBasicAssessmentItemSession();
     $itemSessionControl = new ItemSessionControl();
     $itemSessionControl->setValidateResponses(false);
     $itemSession->setItemSessionControl($itemSessionControl);
     $itemSession->beginItemSession();
     $itemSession->beginAttempt();
     $responses = new State();
     $responses->setVariable(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new Identifier('ChoiceD')));
     $itemSession->endAttempt($responses);
 }