/** * Apply the current SetCorrectResponse rule on the current state. * * A RuleProcessingException will be thrown if: * * * No variable corresponds to the given identifier in the current state. * * The target variable is not a ResponseVariable. * * The baseType and/or cardinality of the value to be set does not correspond to the baseType and/or cardinality of the target variable. * * An error occurs while processing the expression representing the value to be set. * * @throws \qtism\runtime\rules\RuleProcessingException */ public function process() { $rule = $this->getRule(); $state = $this->getState(); $variableIdentifier = $rule->getIdentifier(); $var = $state->getVariable($variableIdentifier); if (is_null($var) === true) { $msg = "No variable with identifier '{$variableIdentifier}' to be set in the current state."; throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE); } elseif (!$var instanceof ResponseVariable) { $msg = "The variable to set '{$variableIdentifier}' is not an instance of 'ResponseVariable'."; throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE); } try { $expressionEngine = new ExpressionEngine($rule->getExpression(), $state); $val = $expressionEngine->process(); $var->setCorrectResponse($val); } catch (InvalidArgumentException $e) { $varBaseType = BaseType::getNameByConstant($var->getBaseType()) === false ? 'noBaseType' : BaseType::getNameByConstant($var->getBaseType()); $varCardinality = Cardinality::getNameByConstant($var->getCardinality()); // The affected value does not match the baseType of the variable $var. $msg = "Unable to set value {$val} to variable '{$variableIdentifier}' (cardinality = {$varCardinality}, baseType = {$varBaseType})."; throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_BASETYPE, $e); } catch (ExpressionProcessingException $e) { $msg = "An error occured while processing the expression bound with the 'SetCorrectResponse' rule."; throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e); } }
/** * Marshall a BaseValue object into a DOMElement object. * * @param \qtism\data\QtiComponent $component A BaseValue object. * @return \DOMElement The according DOMElement object. */ protected function marshall(QtiComponent $component) { $element = static::getDOMCradle()->createElement($component->getQtiClassName()); self::setDOMElementAttribute($element, 'baseType', BaseType::getNameByConstant($component->getBaseType())); self::setDOMElementValue($element, $component->getValue()); return $element; }
/** * Checks whether or not $value: * * * is an instance of OutcomeVariable * * has a 'duration' QTI baseType. * * has 'single' QTI cardinality. * * @throws \InvalidArgumentException If one or more of the conditions above are not respected. */ protected function checkType($value) { parent::checkType($value); if (!$value instanceof OutcomeVariable) { $className = get_class($value); $msg = "The DurationStore only aims at storing OutcomeVariable objects, {$className} object given."; throw new InvalidArgumentException($msg); } if (($bt = $value->getBaseType()) !== BaseType::DURATION) { $baseTypeName = BaseType::getNameByConstant($bt); $msg = "The DurationStore only aims at storing OutcomeVariable objects with a 'duration' baseType, "; $msg .= "'{$baseTypeName}' baseType given "; $id = $value->getIdentifier(); $msg .= "for variable '{$id}'."; throw new InvalidArgumentException($msg); } if (($bt = $value->getCardinality()) !== Cardinality::SINGLE) { $cardinalityName = Cardinality::getNameByConstant($bt); $msg = "The DurationStore only aims at storing OutcomeVariable objects with a 'single' cardinality, "; $msg .= "'{$cardinalityName}' cardinality given "; $id = $value->getIdentifier(); $msg .= "for variable '{$id}'."; throw new InvalidArgumentException($msg); } }
/** * Marshall a TestVariable object in its DOMElement equivalent. * * @param \qtism\data\QtiComponent A TestVariable object. * @return \DOMElement The corresponding testVariable QTI element. */ protected function marshall(QtiComponent $component) { $element = parent::marshall($component); self::setDOMElementAttribute($element, 'variableIdentifier', $component->getVariableIdentifier()); $baseType = $component->getBaseType(); if ($baseType != -1) { self::setDOMElementAttribute($element, 'baseType', BaseType::getNameByConstant($baseType)); } $weightIdentifier = $component->getWeightIdentifier(); if (!empty($weightIdentifier)) { self::setDOMElementAttribute($element, 'weightIdentifier', $weightIdentifier); } return $element; }
/** * Marshall a VariableDeclaration object into a DOMElement object. * * @param \qtism\data\QtiComponent $component An OutcomeDeclaration object. * @return \DOMElement The according DOMElement object. */ protected function marshall(QtiComponent $component) { $element = static::getDOMCradle()->createElement($component->getQtiClassName()); self::setDOMElementAttribute($element, 'identifier', $component->getIdentifier()); self::setDOMElementAttribute($element, 'cardinality', Cardinality::getNameByConstant($component->getCardinality())); if ($component->getBaseType() != -1) { self::setDOMElementAttribute($element, 'baseType', BaseType::getNameByConstant($component->getBaseType())); } // deal with default value. if ($component->getDefaultValue() != null) { $defaultValue = $component->getDefaultValue(); $defaultValueMarshaller = $this->getMarshallerFactory()->createMarshaller($defaultValue, array($component->getBaseType())); $element->appendChild($defaultValueMarshaller->marshall($defaultValue)); } return $element; }
/** * Throw an InvalidArgumentException depending on a given qti:baseType * and an in-memory PHP value. * * @param int $baseType A value from the BaseType enumeration. * @param mixed $value A given PHP primitive value. * @throws \InvalidArgumentException In any case. */ public static function throwBaseTypeTypingError($baseType, $value) { $givenValue = gettype($value) == 'object' ? get_class($value) : gettype($value) . ':' . $value; $acceptedTypes = BaseType::getNameByConstant($baseType); $msg = "The value '{$givenValue}' is not compliant with the '{$acceptedTypes}' baseType."; throw new InvalidArgumentException($msg); }
/** * Transmit a test-level QtiSm Runtime Variable to the target Result Server as a test result. * * @param mixed $variable An OutcomeVariable object to be transmitted to the target Result Server. * @param string $transmissionId A unique identifier that identifies uniquely the visited test. * @param string $testUri An optional URL that identifies uniquely the test the $variable comes from. */ public function transmitTestVariable($variable, $transmissionId, $testUri = '') { $resultVariable = new taoResultServer_models_classes_OutcomeVariable(); $resultVariable->setIdentifier($variable->getIdentifier()); $resultVariable->setBaseType(BaseType::getNameByConstant($variable->getBaseType())); $resultVariable->setCardinality(Cardinality::getNameByConstant($variable->getCardinality())); $value = $variable->getValue(); $resultVariable->setValue(self::transformValue($value)); try { $this->getResultServer()->storeTestVariable($testUri, $resultVariable, $transmissionId); } catch (Exception $e) { $msg = "An error occured while transmitting a Response Variable to the target result server."; $code = taoQtiCommon_helpers_ResultTransmissionException::OUTCOME; throw new taoQtiCommon_helpers_ResultTransmissionException($msg, $code); } }
/** * Process the setOutcomeValue/setTemplateValue rule. * * A RuleProcessingException will be thrown if: * * * The variable does not exist. * * The requested variable is not an OutcomeVariable/TemplateVariable. * * The variable's baseType does not match the baseType of the affected value. * * An error occurs while processing 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 = "No variable with identifier '{$identifier}' to be set in the current state."; throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE); } elseif (Reflection::isInstanceOf($var, $this->getVariableType()) === false) { $msg = "The variable to set '{$identifier}' is not an instance of '" . $this->getVariableType() . "'."; throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE); } // Process the expression. // Its result will be the value to set to the target variable. try { $expressionEngine = new ExpressionEngine($rule->getExpression(), $state); $val = $expressionEngine->process(); } catch (ExpressionProcessingException $e) { $msg = "An error occured while processing the expression bound with the '" . Reflection::shortClassName($rule) . "' rule."; throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e); } // The variable exists, its new value is processed. try { // juggle a little bit (int -> float, float -> int) if ($val !== null && $var->getCardinality() === Cardinality::SINGLE) { $baseType = $var->getBaseType(); if ($baseType === BaseType::INTEGER && $val instanceof Float) { $val = new Integer(intval($val->getValue())); } elseif ($baseType === BaseType::FLOAT && $val instanceof Integer) { $val = new Float(floatval($val->getValue())); } } $var->setValue($val); } catch (InvalidArgumentException $e) { $varBaseType = BaseType::getNameByConstant($var->getBaseType()) === false ? 'noBaseType' : BaseType::getNameByConstant($var->getBaseType()); $varCardinality = Cardinality::getNameByConstant($var->getCardinality()); // The affected value does not match the baseType of the variable $var. $msg = "Unable to set value {$val} to variable '{$identifier}' (cardinality = {$varCardinality}, baseType = {$varBaseType})."; throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_BASETYPE, $e); } }
/** * Unmarshall a DOMElement object corresponding to a QTI MatchTable element. * * @param DOMElement $element A DOMElement object. * @return QtiComponent A MatchTable object. * @throws UnmarshallingException If the $element to unmarshall has no matchTableEntry children. */ protected function unmarshall(DOMElement $element) { $matchTableEntryElements = $element->getElementsByTagName('matchTableEntry'); if ($matchTableEntryElements->length > 0) { $matchTableEntries = new MatchTableEntryCollection(); for ($i = 0; $i < $matchTableEntryElements->length; $i++) { $marshaller = $this->getMarshallerFactory()->createMarshaller($matchTableEntryElements->item($i), array($this->getBaseType())); $matchTableEntries[] = $marshaller->unmarshall($matchTableEntryElements->item($i)); } $object = new MatchTable($matchTableEntries); if (($defaultValue = static::getDOMElementAttributeAs($element, 'defaultValue')) !== null) { try { $defaultValue = Utils::stringToDatatype($defaultValue, $this->getBaseType()); $object->setDefaultValue($defaultValue); } catch (InvalidArgumentException $e) { $strType = BaseType::getNameByConstant($this->getBaseType()); $msg = "Unable to transform '{$defaultValue}' in a {$strType}."; throw new UnmarshallingException($msg, $element, $e); } } return $object; } else { $msg = "A QTI matchTable element must contain at least one matchTableEntry element."; throw new UnmarshallingException($msg, $element); } }
/** * Transform a PCI JSON representation of QTI data into the QTISM runtime model. * * @param string|array $json The json data to be transformed. * @throws UnmarshallingException If an error occurs while processing $json. * @return null|qtism\common\datatypes\QtiDataType|array */ public function unmarshall($json) { if (is_string($json) === true) { $tmpJson = @json_decode($json, true); if ($tmpJson === null) { // An error occured while decoding. $msg = "An error occured while decoding the following JSON data '" . mb_substr($json, 0, 30, 'UTF-8') . "...'."; $code = UnmarshallingException::JSON_DECODE; throw new UnmarshallingException($msg, $code); } $json = $tmpJson; } if (is_array($json) === false || count($json) === 0) { $msg = "The '" . get_class($this) . "::unmarshall' method only accepts a JSON string or a non-empty array as argument, '"; if (is_object($json) === true) { $msg .= get_class($json); } else { $msg .= gettype($json); } $msg .= "' given."; $code = UnmarshallingException::NOT_SUPPORTED; throw new UnmarshallingException($msg, $code); } if (Arrays::isAssoc($json) === false) { $msg = "The '" . get_class($this) . "::unmarshall' does not accepts non-associative arrays."; $code = UnmarshallingException::NOT_SUPPORTED; throw new UnmarshallingException($msg, $code); } // Check whether or not $json is a state (no 'base' nor 'list' keys found), // a base, a list or a record. $keys = array_keys($json); if (in_array('base', $keys) === true) { // This is a base. return $this->unmarshallUnit($json); } else { if (in_array('list', $keys) === true) { $keys = array_keys($json['list']); if (isset($keys[0]) === false) { $msg = "No baseType provided for list."; throw new UnmarshallingException($msg, UnmarshallingException::NOT_PCI); } $baseType = BaseType::getConstantByName($keys[0]); if ($baseType === false) { $msg = "Unknown QTI baseType '" . $keys[0] . "'."; $code = UnmarshallingException::NOT_PCI; throw new UnmarshallingException($msg, $code); } $returnValue = new MultipleContainer($baseType); // This is a list. foreach ($json['list'][$keys[0]] as $v) { try { if ($v === null) { $returnValue[] = $this->unmarshallUnit(array('base' => $v)); } else { $returnValue[] = $this->unmarshallUnit(array('base' => array($keys[0] => $v))); } } catch (InvalidArgumentException $e) { $strBaseType = BaseType::getNameByConstant($baseType); $msg = "A value is not compliant with the '{$strBaseType}' baseType."; $code = UnmarshallingException::NOT_PCI; throw new UnmarshallingException($msg, $code); } } return $returnValue; } else { if (in_array('record', $keys) === true) { // This is a record. $returnValue = new RecordContainer(); if (count($json['record']) === 0) { return $returnValue; } foreach ($json['record'] as $v) { if (isset($v['name']) === false) { $msg = "No 'name' key found in record field."; $code = UnmarshallingException::NOT_PCI; throw new UnmarshallingException($msg, $code); } if (isset($v['base']) === true || array_key_exists('base', $v) && $v['base'] === null) { $unit = array('base' => $v['base']); } else { // No value found, let's go for a null value. $unit = array('base' => null); } $returnValue[$v['name']] = $this->unmarshallUnit($unit); } return $returnValue; } else { // This is a state. $state = array(); foreach ($json as $k => $j) { $state[$k] = $this->unmarshall($j); } return $state; } } } }
/** * Process the MapResponsePoint Expression. * * An ExpressionProcessingException is throw if: * * * The expression's identifier attribute does not point a variable in the current State object. * * The targeted variable is not a ResponseVariable object. * * The targeted variable has no areaMapping. * * The target variable has the RECORD cardinality. * * @return float A transformed float value according to the areaMapping of the target variable. * @throws \qtism\runtime\expressions\ExpressionProcessingException */ public function process() { $expr = $this->getExpression(); $identifier = $expr->getIdentifier(); $state = $this->getState(); $var = $state->getVariable($identifier); if (!is_null($var)) { if ($var instanceof ResponseVariable) { $areaMapping = $var->getAreaMapping(); if (!is_null($areaMapping)) { // Correct cardinality ? if ($var->getBaseType() === BaseType::POINT && ($var->isSingle() || $var->isMultiple())) { // We can begin! // -- Null value, nothing will match if ($var->isNull()) { return new Float($areaMapping->getDefaultValue()); } if ($var->isSingle()) { $val = new MultipleContainer(BaseType::POINT, array($state[$identifier])); } else { $val = $state[$identifier]; } $result = 0; $mapped = array(); foreach ($val as $point) { foreach ($areaMapping->getAreaMapEntries() as $areaMapEntry) { $coords = $areaMapEntry->getCoords(); if (!in_array($coords, $mapped) && $coords->inside($point)) { $mapped[] = $coords; $result += $areaMapEntry->getMappedValue(); } } } // If no relevant mapping found, return the default. if (count($mapped) === 0) { return new Float($areaMapping->getDefaultValue()); } else { // Check upper and lower bound. if ($areaMapping->hasLowerBound() && $result < $areaMapping->getLowerBound()) { return new Float($areaMapping->getLowerBound()); } elseif ($areaMapping->hasUpperBound() && $result > $areaMapping->getUpperBound()) { return new Float($areaMapping->getUpperBound()); } else { return new Float(floatval($result)); } } } else { if ($var->isRecord()) { $msg = "The MapResponsePoint expression cannot be applied to RECORD variables."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::WRONG_VARIABLE_CARDINALITY); } else { $strBaseType = BaseType::getNameByConstant($var->getBaseType()); $msg = "The MapResponsePoint expression applies only on variables with baseType 'point', baseType '{$strBaseType}' given."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::WRONG_VARIABLE_BASETYPE); } } } else { $msg = "Variable with identifier '{$identifier}' has no areaMapping."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::INCONSISTENT_VARIABLE); } } else { $msg = "The variable with identifier '{$identifier}' is not a ResponseVariable."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::WRONG_VARIABLE_TYPE); } } else { $msg = "No variable with identifier '{$identifier}' could be found in the current State object."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::NONEXISTENT_VARIABLE); } }
/** * Marshall a Value object into a DOMElement object. * * @param \qtism\data\QtiComponent $component A Value object. * @return \DOMElement The according DOMElement object. */ protected function marshall(QtiComponent $component) { $element = static::getDOMCradle()->createElement($component->getQtiClassName()); $fieldIdentifer = $component->getFieldIdentifier(); $baseType = $component->getBaseType(); self::setDOMElementValue($element, $component->getValue()); if (!empty($fieldIdentifer)) { static::setDOMElementAttribute($element, 'fieldIdentifier', $fieldIdentifer); } if ($component->isPartOfRecord() && $baseType >= 0) { static::setDOMElementAttribute($element, 'baseType', BaseType::getNameByConstant($baseType)); } return $element; }
/** * Marshall a single unit of QTI data. * * @param \qtism\runtime\common\State|\qtism\common\datatypes\QtiDatatype|null $unit * @return array An array representing the JSON data to be encoded later on. */ protected function marshallUnit($unit) { if (is_null($unit) === true) { $json = array('base' => null); } elseif ($unit instanceof QtiScalar) { $json = $this->marshallScalar($unit); } elseif ($unit instanceof MultipleContainer) { $json = array(); $strBaseType = BaseType::getNameByConstant($unit->getBaseType()); $json['list'] = array($strBaseType => array()); foreach ($unit as $u) { $data = $this->marshallUnit($u); $json['list'][$strBaseType][] = $data['base'][$strBaseType]; } } elseif ($unit instanceof RecordContainer) { $json = array(); $json['record'] = array(); foreach ($unit as $k => $u) { $data = $this->marshallUnit($u); $jsonEntry = array(); $jsonEntry['name'] = $k; if (isset($data['base']) === true || $data['base'] === null) { // Primitive base type. $jsonEntry['base'] = $data['base']; } else { // A nested list. $jsonEntry['list'] = $data['list']; } $json['record'][] = $jsonEntry; } } else { $json = $this->marshallComplex($unit); } return $json; }
/** * 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); } }
/** * @dataProvider invalidBaseTypeConstantProvider */ public function testGetNameByConstantInvalidBaseType($constant) { $this->assertFalse(BaseType::getNameByConstant($constant)); }