/** * Process the Delete operator. * * @return \qtism\common\collections\Container A new container derived from the second sub-expression with all instances of the first sub-expression removed, or NULL if either sub-expression is considered to be NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull() === true) { return null; } if ($operands->sameBaseType() === false) { $msg = "The Delete operator only accepts operands with the same baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } $operand1 = $operands[0]; if (RuntimeUtils::inferCardinality($operand1) !== Cardinality::SINGLE) { $msg = "The first operand of the Delete operator must have the single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } $operand2 = $operands[1]; $cardinality = RuntimeUtils::inferCardinality($operand2); if ($cardinality !== Cardinality::MULTIPLE && $cardinality !== Cardinality::ORDERED) { $msg = "The second operand of the Delete operator must have a cardinality or multiple or ordered."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } $returnedBaseType = RuntimeUtils::inferBaseType($operand1); $returnValue = $cardinality === Cardinality::MULTIPLE ? new MultipleContainer($returnedBaseType) : new OrderedContainer($returnedBaseType); foreach ($operand2 as $value) { if ($value === $operand1 || $operand1 instanceof Comparable && $operand1->equals($value) === true) { // This is the same value, it will not be included in the returned value. continue; } else { $returnValue[] = $value; } } return $returnValue; }
/** * Process the Member operator. * * @return boolean Whether the first operand is contained by the second one as a boolean value, or NULL if any of the sub-expressions are NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull() === true) { return null; } if ($operands->sameBaseType() === false) { $msg = "The Member operator only accepts values with the same baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } $operand1 = $operands[0]; $operand2 = $operands[1]; // The first expression must have single cardinality. if (CommonUtils::inferCardinality($operand1) !== Cardinality::SINGLE) { $msg = "The first operand of the Member operator must have a single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } // The second expression must have multiple or ordered cardinality. $cardinality = CommonUtils::inferCardinality($operand2); if ($cardinality !== Cardinality::MULTIPLE && $cardinality !== Cardinality::ORDERED) { $msg = "The second operand of the Member operator must have a multiple or ordered cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } return new Boolean($operand2->contains($operand1)); }
/** * Process the TemplateConstraint rule. It simply throws a RuleProcessingException with * the special code RuleProcessingException::TEMPLATE_CONSTRAINT_UNSATISFIED to warn client * code that the expression related to the constraint returned false or null. * * @throws \qtism\runtime\rules\RuleProcessingException with code = RuleProcessingException::TEMPLATE_CONSTRAINT_UNSATISFIED. */ public function process() { $state = $this->getState(); $rule = $this->getRule(); $expr = $rule->getExpression(); $expressionEngine = new ExpressionEngine($expr, $state); $val = $expressionEngine->process(); if (Utils::isNull($val) || $val->getValue() === false) { $msg = "Unsatisfied Template Constraint."; throw new RuleProcessingException($msg, $this, RuleProcessingException::TEMPLATE_CONSTRAINT_UNSATISFIED); } }
/** * Process the current expression. * * @return \qtism\runtime\common\OrderedContainer|null An OrderedContainer object or NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if (count($operands) === 0) { return null; } if ($operands->exclusivelySingleOrOrdered() === false) { $msg = "The Ordered operator only accepts operands with single or ordered cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } $refType = null; $returnValue = null; foreach ($operands as $operand) { if (is_null($operand) || $operand instanceof OrderedContainer && $operand->isNull()) { // As per specs, ignore. continue; } else { if ($refType !== null) { // A reference type as already been identifier. if (CommonUtils::inferBaseType($operand) === $refType) { // $operand can be added to $returnValue. static::appendValue($returnValue, $operand); } else { // baseType mismatch. $msg = "The Ordered operator only accepts values with a similar baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } } elseif (($discoveryType = CommonUtils::inferBaseType($operand)) !== false) { // First value being identified as non-null. $refType = $discoveryType; $returnValue = new OrderedContainer($refType); static::appendValue($returnValue, $operand); } } } return $returnValue; }
/** * Create a QTI Runtime value from Data Model ValueCollection * * @param \qtism\data\state\ValueCollection $valueCollection A collection of qtism\data\state\Value objects. * @param integer $baseType The baseType the Value objects in the ValueCollection must respect. * @param integer $cardinality The cardinality the Value objects in the ValueCollection must respect. * @throws \UnexpectedValueException If $baseType or/and $cardinality are not respected by the Value objects in the ValueCollection. * @return mixed The resulting QTI Runtime value (primitive or container depending on baseType/cardinality). */ protected static function dataModelValuesToRuntime(ValueCollection $valueCollection, $baseType, $cardinality) { // Cardinality? // -> Single? Multiple? Ordered? Record? if ($cardinality === Cardinality::SINGLE) { // We should find a single value in the DefaultValue's values. if (count($valueCollection) == 1) { $dataModelValue = RuntimeUtils::valueToRuntime($valueCollection[0]->getValue(), $baseType); return $dataModelValue; } else { // The Data Model is in an inconsistent state. // This should be handled by the Data Model but // I prefer to be defensive. $msg = "A Data Model VariableDeclaration with 'single' cardinality must contain a single value, "; $msg .= count($valueCollection) . " value(s) found."; throw new UnexpectedValueException($msg); } } else { // Multiple|Ordered|Record, use a container. $container = null; try { // Create the appropriate Container object. $className = ucfirst(Cardinality::getNameByConstant($cardinality)) . 'Container'; $nsClassName = 'qtism\\runtime\\common\\' . $className; $callback = array($nsClassName, 'createFromDataModel'); $container = call_user_func_array($callback, array($valueCollection, $baseType)); return $container; // return container. } catch (InvalidArgumentException $e) { $msg = "The default value found in the Data Model Variable Declaration is not consistent. "; $msg .= "The values must have a baseType compliant with the baseType of the VariableDeclaration."; $msg .= "If the VariableDeclaration's cardinality is 'record', make sure the values it contains have "; $msg .= "fieldIdentifiers."; throw new UnexpectedValueException($msg, 0, $e); } } }
/** * Set the default value of the Variable. * * @param \qtism\common\datatypes\QtiDatatype|null $defaultValue A QtiDatatype object or null. * @throws \InvalidArgumentException If $defaultValue's type is not compliant with the qti:baseType of the Variable. */ public function setDefaultValue(QtiDatatype $defaultValue = null) { if (Utils::isBaseTypeCompliant($this->getBaseType(), $defaultValue) && Utils::isCardinalityCompliant($this->getCardinality(), $defaultValue)) { $this->defaultValue = $defaultValue; return; } else { Utils::throwBaseTypeTypingError($this->getBaseType(), $defaultValue); } }
/** * @dataProvider isValidVariableIdentifierProvider * * @param string $string * @param boolean $expected */ public function testIsValidVariableIdentifier($string, $expected) { $this->assertSame($expected, Utils::isValidVariableIdentifier($string)); }
/** * Set the identifier string. * * @param string $identifier A prefixed identifier. * @throws \InvalidArgumentException If $identifier is not a valid prefixed identifier. */ protected function setIdentifier($identifier) { if (Utils::isValidVariableIdentifier($identifier)) { $this->identifier = $identifier; } else { $msg = "The identifier '{$identifier}' is not a valid QTI Variable Name Identifier."; throw new InvalidArgumentException($msg); } }
/** * Set the correct response. * * @param \qtism\common\datatypes\QtiDatatype|null $correctResponse A QtiDatatype object or null. * @throws \InvalidArgumentException If $correctResponse does not match baseType and/or cardinality of the variable. */ public function setCorrectResponse(QtiDatatype $correctResponse = null) { if ($correctResponse === null) { $this->correctResponse = null; } elseif (Utils::isBaseTypeCompliant($this->getBaseType(), $correctResponse) === true && Utils::isCardinalityCompliant($this->getCardinality(), $correctResponse) === true) { $this->correctResponse = $correctResponse; } else { $msg = "The given correct response is not compliant with the associated response variable."; throw new InvalidArgumentException($msg); } }
/** * @see \qtism\runtime\common\Processable::process() */ public function process() { $expression = $this->getExpression(); return RuntimeUtils::valueToRuntime($expression->getValue(), $expression->getBaseType()); }
/** * Whether the item of the session has been attempted (at least once) and for which at least one response was given. * * @return boolean */ public function isResponded() { if ($this->isPresented() === false) { return false; } $excludedResponseVariables = array('numAttempts', 'duration'); foreach ($this->getKeys() as $k) { $var = $this->getVariable($k); if ($var instanceof ResponseVariable && in_array($k, $excludedResponseVariables) === false) { $value = $var->getValue(); $defaultValue = $var->getDefaultValue(); if (Utils::isNull($value) === true) { if (Utils::isNull($defaultValue) === false) { return true; } } else { if ($value->equals($defaultValue) === false) { return true; } } } } return false; }
/** * @dataProvider equalsProvider * * @param QtiDatatype $a * @param QtiDatatype $b * @param boolean $expected */ public function testEquals(QtiDatatype $a = null, QtiDatatype $b = null, $expected) { $this->assertSame($expected, Utils::equals($a, $b)); }
/** * Create a RecordContainer object from a Data Model ValueCollection object. * * @param \qtism\data\state\ValueCollection $valueCollection A collection of qtism\data\state\Value objects. * @return \qtism\runtime\common\RecordContainer A Container object populated with the values found in $valueCollection. * @throws \InvalidArgumentException If a value from $valueCollection is not compliant with the QTI Runtime Model or the container type or if a value has no fieldIdentifier. */ public static function createFromDataModel(ValueCollection $valueCollection) { $container = new static(); foreach ($valueCollection as $value) { $fieldIdentifier = $value->getFieldIdentifier(); if (!empty($fieldIdentifier)) { $container[$value->getFieldIdentifier()] = RuntimeUtils::valueToRuntime($value->getValue(), $value->getBaseType()); } else { $msg = "Cannot include qtism\\data\\state\\Value '" . $value->getValue() . "' in the RecordContainer "; $msg .= "because it has no fieldIdentifier specified."; throw new InvalidArgumentException($msg); } } return $container; }
/** * Wether the collection is composed of values with the same cardinality. Please * note that: * * * If the OperandsCollection is empty, false is returned. * * If the OperandsCollection contains a NULL value or a NULL container (empty), false is returned * * @return boolean */ public function sameCardinality() { $operandsCount = count($this); if ($operandsCount > 0 && !$this->containsNull()) { $refType = RuntimeUtils::inferCardinality($this[0]); for ($i = 1; $i < $operandsCount; $i++) { if ($refType !== RuntimeUtils::inferCardinality($this[$i])) { return false; } } return true; } else { return false; } }
/** * Process the Repeat operator. * * Note: NULL values are simply ignored. If all sub-expressions are NULL, NULL is * returned. * * @return \qtism\runtime\common\OrderedContainer An ordered container filled sequentially by evaluating each sub-expressions, repeated a 'numberRepeats' of times. NULL is returned if all sub-expressions are NULL or numberRepeats < 1. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); // get the value of numberRepeats $expression = $this->getExpression(); $numberRepeats = $expression->getNumberRepeats(); if (gettype($numberRepeats) === 'string') { // Variable reference found. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($numberRepeats); $varValue = $state[$varName]; if (is_null($varValue) === true) { $msg = "The variable with name '{$varName}' could not be resolved."; throw new OperatorProcessingException($msg, $this); } elseif ($varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer value."; throw new OperatorProcessingException($msg, $this); } $numberRepeats = $varValue->getValue(); } if ($numberRepeats < 1) { return null; } $result = null; for ($i = 0; $i < $numberRepeats; $i++) { $refType = null; foreach ($operands as $operand) { // If null, ignore if (is_null($operand) || $operand instanceof Container && $operand->isNull()) { continue; } // Check cardinality. if ($operand->getCardinality() !== Cardinality::SINGLE && $operand->getCardinality() !== Cardinality::ORDERED) { $msg = "The Repeat operator only accepts operands with a single or ordered cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } // Check baseType. $currentType = RuntimeUtils::inferBaseType($operand); if ($refType !== null && $currentType !== $refType) { $msg = "The Repeat operator only accepts operands with the same baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } elseif (is_null($result)) { $refType = $currentType; $result = new OrderedContainer($refType); } // Okay we are good... $operandCardinality = RuntimeUtils::inferCardinality($operand); if ($operandCardinality !== Cardinality::ORDERED) { $operand = new OrderedContainer($currentType, array($operand)); } foreach ($operand as $o) { $result[] = $o instanceof QtiDatatype ? clone $o : $o; } } } if (isset($result) && $result->isNull() !== true) { return $result; } else { return null; } }
/** * Write a record field value in the current binary stream. A record field is composed of a key string and a value. * * @param array $recordField An array where index 0 is the key string, and the index 1 is the value. * @throws QtiBinaryStreamAccessException */ public function writeRecordField(array $recordField, $isNull = false) { try { $this->writeBoolean($isNull); $key = $recordField[0]; $this->writeString($key); if ($isNull === false) { $value = $recordField[1]; $baseType = Utils::inferBaseType($value); $this->writeTinyInt($baseType); $toCall = 'write' . ucfirst(BaseType::getNameByConstant($baseType)); call_user_func(array($this, $toCall), $value instanceof Scalar ? $value->getValue() : $value); } } catch (BinaryStreamAccessException $e) { $msg = "An error occured while reading a Record Field."; throw new QtiBinaryStreamAccessException($msg, $this, QtiBinaryStreamAccessException::RECORDFIELD); } }
protected function checkType($value) { if (!Utils::isRuntimeCompliant($value)) { Utils::throwTypingError($value); } }
/** * Set the correct response. * * @param mixed $correctResponse A QTI Runtime compliant object. */ public function setCorrectResponse($correctResponse) { if (Utils::isBaseTypeCompliant($this->getBaseType(), $correctResponse) === true) { $this->correctResponse = $correctResponse; return; } else { if ($correctResponse instanceof Container) { if ($correctResponse->getCardinality() === $this->getCardinality()) { if (get_class($correctResponse) === 'qtism\\runtime\\common\\Container' || $correctResponse->getBaseType() === $this->getBaseType()) { // This is a simple container with no baseType restriction // or a Multiple|Record|Ordered container with a compliant // baseType. $this->correctResponse = $correctResponse; return; } else { $msg = "The baseType of the given container ('" . BaseType::getNameByConstant($correctResponse->getBaseType()) . "') "; $msg .= "is not compliant with "; $msg .= "the baseType of the variable ('" . BaseType::getNameByConstant($this->getBaseType()) . "')."; throw new InvalidArgumentException($msg); } } else { $msg = "The cardinality of the given container ('" . Cardinality::getNameByConstant($value->getCardinality()) . "') "; $msg .= "is not compliant with "; $msg .= "the cardinality of the variable ('" . Cardinality::getNameByConstant($this->getCardinality()) . "')."; throw new InvalidArgumentException($msg); } } } $msg = "The provided value is not compliant with the baseType of the ResponseVariable."; throw new InvalidArgumentException($msg); }
/** * Makes $value compliant with baseType $targetBaseType, if $value is compliant. Otherwise, * the original $value is returned. * * @param mixed $value A QTI Runtime compliant value. * @param integer $targetBaseType The target baseType. * @return mixed The juggled value if needed, otherwise the original value of $value. */ public static function juggle($value, $targetBaseType) { // A lot of people designing QTI items want to put float values // in integer baseType'd variables... So let's go for type juggling! $valueBaseType = RuntimeUtils::inferBaseType($value); if ($valueBaseType !== $targetBaseType && ($value instanceof MultipleContainer || $value instanceof OrderedContainer)) { $class = get_class($value); if ($valueBaseType === BaseType::FLOAT && $targetBaseType === BaseType::INTEGER) { $value = new $class($targetBaseType, self::floatArrayToInteger($value->getArrayCopy())); } else { if ($valueBaseType === BaseType::INTEGER && $targetBaseType === BaseType::FLOAT) { $value = new $class($targetBaseType, self::integerArrayToFloat($value->getArrayCopy())); } else { if ($valueBaseType === BaseType::IDENTIFIER && $targetBaseType === BaseType::STRING) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::STRING && $targetBaseType === BaseType::IDENTIFIER) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::URI && $targetBaseType === BaseType::STRING) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::STRING && $targetBaseType === BaseType::URI) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::URI && $targetBaseType === BaseType::IDENTIFIER) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::IDENTIFIER && $targetBaseType === BaseType::URI) { $value = new $class($targetBaseType, $value->getArrayCopy()); } } } } } } } } } else { if ($valueBaseType !== $targetBaseType) { // Scalar value. if ($valueBaseType === BaseType::FLOAT && $targetBaseType === BaseType::INTEGER) { $value = intval($value); } else { if ($valueBaseType === BaseType::INTEGER && $targetBaseType === BaseType::FLOAT) { $value = floatval($value); } } } } return $value; }
/** * Create a Container object from a Data Model ValueCollection object. * * @param \qtism\data\state\ValueCollection $valueCollection A collection of qtism\data\state\Value objects. * @return \qtism\common\collections\Container A Container object populated with the values found in $valueCollection. * @throws \InvalidArgumentException If a value from $valueCollection is not compliant with the QTI Runtime Model or the container type. */ public static function createFromDataModel(ValueCollection $valueCollection) { $container = new static(); foreach ($valueCollection as $value) { $container[] = RuntimeUtils::valueToRuntime($value->getValue(), $value->getBaseType()); } return $container; }
/** * Process the EqualRounded operator. * * @return boolean|null A boolean with a value of true if the two expressions are numerically equal after rounding and false if they are not. If either sub-expression is NULL, the operator results in NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull()) { return null; } if ($operands->exclusivelySingle() === false) { $msg = "The EqualRounded operator only accepts operands with a single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } if ($operands->exclusivelyNumeric() === false) { $msg = "The EqualRounded operator only accepts operands with an integer or float baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } // delegate the rounding to the RoundTo operator. $expression = $this->getExpression(); $roundingMode = $expression->getRoundingMode(); $figures = $expression->getFigures(); if (gettype($figures) === 'string') { // Variable reference to deal with. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($figures); $varValue = $state[$varName]; if (is_null($varValue) === true) { $msg = "The variable with name '{$varName}' could not be resolved."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } elseif (!$varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } $figures = $varValue->getValue(); } $rounded = new OperandsCollection(); // will contain the rounded operands. foreach ($operands as $operand) { $baseType = RuntimeUtils::inferBaseType($operand); $subExpression = new BaseValue($baseType, $operand); $roundToExpression = new RoundTo(new ExpressionCollection(array($subExpression)), $figures, $roundingMode); $roundToProcessor = new RoundToProcessor($roundToExpression, new OperandsCollection(array($operand))); try { $rounded[] = $roundToProcessor->process(); } catch (OperatorProcessingException $e) { $msg = "An error occured while rounding '{$operand}'."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::LOGIC_ERROR, $e); } } return new Boolean($rounded[0]->getValue() == $rounded[1]->getValue()); }
/** * Processes all values of a record container and merge them into * a single string. * * @param RecordContainer $variable The record to process. * @return string All the key/values delimited by printedVariable->delimiter. Indicator between keys and values is defined by printedVariable->mappingIndicator. */ private function processRecord(Variable $variable) { $processedValues = array(); $baseType = $variable->getBaseType(); $mappingIndicator = $this->getComponent()->getMappingIndicator(); foreach ($variable->getValue() as $k => $v) { $processedValues[] = "{$k}{$mappingIndicator}" . $this->processValue(Utils::inferBaseType($v), $v); } return implode($this->getComponent()->getDelimiter(), $processedValues); }
/** * Process the LookupOutcomeValue rule. * * A RuleProcessingException will be thrown if: * * * The outcome variable to set does not exist. * * The variable to set is not an OutcomeVariable * * The outcome variable's baseType does not match the baseType of the affected value (the result of the bound expression). * * The outcome variable's declaration has no associated lookup table. * * The variable's declaration contains a matchTable but the result of the bound expression is not an integer. * * The variable's declaration contains an interpolationTable but the result of the bound expression is not an integer, nor a float. * * There is no associated table in the variable's declaration. * * An error occurs during the processing of the related expression. * * @throws \qtism\runtime\rules\RuleProcessingException If one of the error described above arise. */ public function process() { $state = $this->getState(); $rule = $this->getRule(); $identifier = $rule->getIdentifier(); $var = $state->getVariable($identifier); if (is_null($var) === true) { $msg = "The variable to set '{$identifier}' does not exist in the current state."; throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE); } elseif (!$var instanceof OutcomeVariable) { $msg = "The variable to set '{$identifier}' is not an OutcomeVariable."; throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE); } $expression = $rule->getExpression(); $expressionEngine = new ExpressionEngine($expression, $state); try { $val = $expressionEngine->process(); // Let's lookup the associated table. $table = $var->getLookupTable(); if (is_null($table) === true) { $msg = "No lookupTable in declaration of variable '{$identifier}'."; throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR); } // $targetVal = The value that will be set to the target variable. // // As per specs: // The default outcome value to be used when no matching table // entry is found. If omitted, the NULL value is used. $targetVal = $table->getDefaultValue(); if ($table instanceof InterpolationTable) { if (!$val instanceof Float && !$val instanceof Integer && !$val instanceof Duration) { $msg = "The value of variable '{$identifier}' must be integer, float or duration when used with an interpolationTable"; throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR); } foreach ($table->getInterpolationTableEntries() as $entry) { $lowerBound = $entry->getSourceValue(); $includeBoundary = $entry->doesIncludeBoundary(); if ($includeBoundary === true && $val->getValue() <= $lowerBound) { $targetVal = $entry->getTargetValue(); break; } elseif ($includeBoundary === false && $val->getValue() < $lowerBound) { $targetVal = $entry->getTargetValue(); break; } } } else { // $table instanceof MatchTable if (!$val instanceof Integer) { $msg = "The value of the variable '{$identifier}' must be integer when used with a matchTable."; throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR); } foreach ($table->getMatchTableEntries() as $entry) { if ($entry->getSourceValue() === $val->getValue()) { $targetVal = $entry->getTargetValue(); break; } } } // assign target value try { $finalVal = RuntimeUtils::valueToRuntime($targetVal, $var->getBaseType()); $state[$identifier] = $finalVal; } catch (InvalidArgumentException $e) { // $targetVal's baseType not compliant with target variable's baseType. $msg = "The looked up value's baseType is not compliant with the baseType of variable '{$identifier}'."; throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR); } } catch (ExpressionProcessingException $e) { $msg = "An error occured while processing the expression bound to the lookupOutcomeValue rule."; throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e); } }
protected function checkType($value) { parent::checkType($value); if (!Utils::isBaseTypeCompliant($this->getBaseType(), $value)) { Utils::throwBaseTypeTypingError($this->getBaseType(), $value); } }
/** * Write an AssessmetnItemSession from the current binary stream. * * @param \qtism\runtime\storage\common\AssessmentTestSeeker $seeker The AssessmentTestSeeker object from where the position of components will be pulled out. * @param \qtism\runtime\tests\AssessmentItemSession $session An AssessmentItemSession object. * @throws \qtism\runtime\storage\binary\QtiBinaryStreamAccessException */ public function writeAssessmentItemSession(AssessmentTestSeeker $seeker, AssessmentItemSession $session) { try { $this->writeShort($seeker->seekPosition($session->getAssessmentItem())); $this->writeTinyInt($session->getState()); $this->writeTinyInt($session->getNavigationMode()); $this->writeTinyInt($session->getSubmissionMode()); $this->writeBoolean($session->isAttempting()); $isItemSessionControlDefault = $session->getItemSessionControl()->isDefault(); if ($isItemSessionControlDefault === true) { $this->writeBoolean(false); } else { $this->writeBoolean(true); $this->writeShort($seeker->seekPosition($session->getItemSessionControl())); } $this->writeTinyInt($session['numAttempts']->getValue()); $this->writeDuration($session['duration']); $this->writeString($session['completionStatus']->getValue()); $timeReference = $session->getTimeReference(); if (is_null($timeReference) === true) { // Describe that we have no time reference for the session. $this->writeBoolean(false); } else { // Describe that we have a time reference for the session. $this->writeBoolean(true); // Write the time reference. $this->writeDateTime($timeReference); } // Write the session variables. // (minus the 3 built-in variables) $varCount = count($session) - 3; $this->writeTinyInt($varCount); $itemOutcomes = $session->getAssessmentItem()->getOutcomeDeclarations(); $itemResponses = $session->getAssessmentItem()->getResponseDeclarations(); $itemTemplates = $session->getAssessmentItem()->getTemplateDeclarations(); foreach ($session->getKeys() as $varId) { if (in_array($varId, array('numAttempts', 'duration', 'completionStatus')) === false) { $var = $session->getVariable($varId); if ($var instanceof OutcomeVariable) { $variableDeclaration = $itemOutcomes[$varId]; $variable = OutcomeVariable::createFromDataModel($variableDeclaration); $varNature = 0; } elseif ($var instanceof ResponseVariable) { $variableDeclaration = $itemResponses[$varId]; $variable = ResponseVariable::createFromDataModel($variableDeclaration); $varNature = 1; } elseif ($var instanceof TemplateVariable) { $variableDeclaration = $itemTemplates[$varId]; $variable = TemplateVariable::createFromDataModel($variableDeclaration); $varNature = 2; } try { $this->writeShort($varNature); $this->writeShort($seeker->seekPosition($variableDeclaration)); // If defaultValue or correct response is different from what's inside // the variable declaration, just write it. $hasDefaultValue = !Utils::equals($variable->getDefaultValue(), $var->getDefaultValue()); $hasCorrectResponse = false; if ($varNature === 1 && !Utils::equals($variable->getCorrectResponse(), $var->getCorrectResponse())) { $hasCorrectResponse = true; } $this->writeBoolean($hasDefaultValue); $this->writeBoolean($hasCorrectResponse); $this->writeVariableValue($var, self::RW_VALUE); if ($hasDefaultValue === true) { $this->writeVariableValue($var, self::RW_DEFAULTVALUE); } if ($hasCorrectResponse === true) { $this->writeVariableValue($var, self::RW_CORRECTRESPONSE); } } catch (OutOfBoundsException $e) { $msg = "No variable found in the assessmentTest tree structure."; throw new QtiBinaryStreamAccessException($msg, $this, QtiBinaryStreamAccessException::ITEM_SESSION, $e); } } } // Write shuffling states if any. $shufflingStates = $session->getShufflingStates(); $this->writeTinyInt(count($shufflingStates)); foreach ($shufflingStates as $shufflingState) { $this->writeShufflingState($shufflingState); } } catch (BinaryStreamAccessException $e) { $msg = "An error occured while writing an assessment item session."; throw new QtiBinaryStreamAccessException($msg, $this, QtiBinaryStreamAccessException::ITEM_SESSION, $e); } catch (OutOfBoundsException $e) { $msg = "No assessmentItemRef found in the assessmentTest tree structure."; throw new QtiBinaryStreamAccessException($msg, $this, QtiBinaryStreamAccessException::ITEM_SESSION, $e); } }