/** * 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); } }
/** * 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); } }
public function testExpressionEngineSum() { $expression = $this->createComponentFromXml(' <sum> <!-- 60 --> <product> <!-- 50 --> <baseValue baseType="integer">10</baseValue> <baseValue baseType="integer">5</baseValue> </product> <divide> <!-- 10 --> <baseValue baseType="integer">50</baseValue> <baseValue baseType="integer">5</baseValue> </divide> </sum> '); $engine = new ExpressionEngine($expression); $result = $engine->process(); $this->assertInstanceOf('qtism\\common\\datatypes\\Float', $result); $this->assertEquals(60.0, $result->getValue()); }
/** * 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); } }
/** * Move to the next item in the route. * * * If there is no more item in the route to be explored the session ends gracefully. * * If there the end of a test part is reached, pending responses are submitted. * * @param boolean $ignoreBranchings Whether or not to ignore branching. * @param boolean $ignorePreConditions Whether or not to ignore preConditions. * @throws AssessmentTestSessionException If the test session is not running or something wrong happens during deffered outcome processing or branching. */ protected function nextRouteItem($ignoreBranchings = false, $ignorePreConditions = false) { if ($this->isRunning() === false) { $msg = "Cannot move to the next position while the state of the test session is INITIAL or CLOSED."; throw new AssessmentTestSessionException($msg, AssessmentTestSessionException::STATE_VIOLATION); } // If the submitted responses are the one of the last // item of the test part, apply deffered response processing. if ($this->getRoute()->isLastOfTestPart() === true && $this->getCurrentSubmissionMode() === SubmissionMode::SIMULTANEOUS) { // The testPart is complete so deffered response processing must take place. $this->defferedResponseProcessing(); } $route = $this->getRoute(); $stop = false; while ($route->valid() === true && $stop === false) { // Branchings? if ($ignoreBranchings === false && $this->getCurrentNavigationMode() === NavigationMode::LINEAR && count($route->current()->getBranchRules()) > 0) { $branchRules = $route->current()->getBranchRules(); for ($i = 0; $i < count($branchRules); $i++) { $engine = new ExpressionEngine($branchRules[$i]->getExpression(), $this); $condition = $engine->process(); if ($condition !== null && $condition->getValue() === true) { $target = $branchRules[$i]->getTarget(); if ($target === 'EXIT_TEST') { $this->endTestSession(); } else { if ($target === 'EXIT_TESTPART') { $this->moveNextTestPart(); } else { if ($target === 'EXIT_SECTION') { $this->moveNextAssessmentSection(); } else { $route->branch($branchRules[$i]->getTarget()); } } } break; } } if ($i >= count($branchRules)) { // No branch rule returned true. Simple move next. $route->next(); } } else { $route->next(); } // Preconditions on target? if ($ignorePreConditions === false && $route->valid() === true) { $preConditions = $route->current()->getPreConditions(); if (count($preConditions) > 0) { for ($i = 0; $i < count($preConditions); $i++) { $engine = new ExpressionEngine($preConditions[$i]->getExpression(), $this); $condition = $engine->process(); if ($condition !== null && $condition->getValue() === true) { // The item must be presented. $stop = true; break; } } } else { $stop = true; } } } $this->selectEligibleItems(); if ($route->valid() === false && $this->isRunning() === true) { $this->endTestSession(); } }
/** * Process the OutcomeCondition/ResponseCondition according to the current state. * * @throws \qtism\runtime\rules\RuleProcessingException */ public function process() { $state = $this->getState(); $this->pushTrail($this->getRule()); $className = ucfirst($this->getQtiNature()); $nsClass = 'qtism\\data\\rules\\' . $className . 'Condition'; $ruleGetter = "get{$className}Rules"; $statementGetter = "get{$className}"; // + 'If'|'ElseIf'|'Else' while (count($this->getTrail()) > 0) { $rule = $this->popTrail(); if (get_class($rule) === $nsClass) { // Let's try for if. $ifStatement = call_user_func(array($rule, $statementGetter . 'If')); $ifExpression = $ifStatement->getExpression(); $exprEngine = new ExpressionEngine($ifExpression, $state); $value = $exprEngine->process(); if ($value !== null && $value->getValue() === true) { // Follow the if. $this->pushTrail(call_user_func(array($ifStatement, $ruleGetter))); } else { // Let's try for else ifs. $followElseIf = false; $elseIfStatements = call_user_func(array($rule, $statementGetter . 'ElseIfs')); foreach ($elseIfStatements as $elseIfStatement) { $elseIfExpression = $elseIfStatement->getExpression(); $exprEngine->setComponent($elseIfExpression); $value = $exprEngine->process(); if ($value !== null && $value->getValue() === true) { // Follow the current else if. $this->pushTrail(call_user_func(array($elseIfStatement, $ruleGetter))); $followElseIf = true; break; } } $elseStatement = call_user_func(array($rule, $statementGetter . 'Else')); if ($followElseIf === false && is_null($elseStatement) === false) { // No else if followed, the last resort is the else. $this->pushTrail(call_user_func(array($elseStatement, $ruleGetter))); } } } else { // $rule is another Rule than OutcomeCondition/ResponseCondition. $processor = $this->getRuleProcessorFactory()->createProcessor($rule); $processor->setState($state); $processor->process(); } } }
/** * 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); } }