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()); }
/** * 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; }