/**
  * 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);
     }
 }
Exemplo n.º 2
0
 /**
  * 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;
 }
Exemplo n.º 3
0
 /**
  * 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);
     }
 }
Exemplo n.º 4
0
 /**
  * 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;
 }
Exemplo n.º 6
0
 /**
  * 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);
     }
 }
Exemplo n.º 8
0
 /**
  * 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;
             }
         }
     }
 }
Exemplo n.º 11
0
 /**
  * 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);
     }
 }
Exemplo n.º 12
0
 /**
  * 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;
 }
Exemplo n.º 13
0
 /**
  * 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);
     }
 }
Exemplo n.º 15
0
 /**
  * @dataProvider invalidBaseTypeConstantProvider
  */
 public function testGetNameByConstantInvalidBaseType($constant)
 {
     $this->assertFalse(BaseType::getNameByConstant($constant));
 }