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());
 }
示例#2
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);
 }
 /**
  * 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;
 }