/**
  * Whether or not a given $test contains preConditions subject to be in force
  * during its execution.
  * 
  * @param AssessmentTest $test
  * @return boolean
  */
 private static function testContainsPreConditions(AssessmentTest $test)
 {
     $testParts = $test->getComponentsByClassName('testPart');
     $containsPreConditions = false;
     foreach ($testParts as $testPart) {
         // PreConditions are only taken into account
         // in linear navigation mode.
         if ($testPart->getNavigationMode() !== NavigationMode::NONLINEAR) {
             $preConditions = $testPart->getComponentsByClassName('preCondition');
             if (count($preConditions) > 0) {
                 $containsPreConditions = true;
                 break;
             }
         }
     }
     return $containsPreConditions;
 }
 public function testWeighted()
 {
     $assessmentItemRef = new ExtendedAssessmentItemRef('Q01', './Q01.xml');
     $weights = new WeightCollection(array(new Weight('weight1', 1.1)));
     $assessmentItemRef->setWeights($weights);
     $assessmentItemRef->addOutcomeDeclaration(new OutcomeDeclaration('var1', BaseType::INTEGER, Cardinality::SINGLE));
     $assessmentItemRef->addOutcomeDeclaration(new OutcomeDeclaration('var2', BaseType::FLOAT, Cardinality::MULTIPLE));
     $assessmentItemRefs = new AssessmentItemRefCollection(array($assessmentItemRef));
     $assessmentTest = new AssessmentTest('A01', 'An assessmentTest');
     $assessmentSection = new AssessmentSection('S01', 'An assessmentSection', true);
     $assessmentSection->setSectionParts($assessmentItemRefs);
     $assessmentSections = new AssessmentSectionCollection(array($assessmentSection));
     $testPart = new TestPart('P01', $assessmentSections);
     $assessmentTest->setTestParts(new TestPartCollection(array($testPart)));
     $sessionManager = new SessionManager();
     $assessmentTestSession = $sessionManager->createAssessmentTestSession($assessmentTest);
     $assessmentTestSession->beginTestSession();
     $assessmentTestSession['Q01.var1'] = new Integer(1337);
     $variableExpr = $this->createComponentFromXml('<variable identifier="Q01.var1" weightIdentifier="weight1" />');
     $variableProcessor = new VariableProcessor($variableExpr);
     $variableProcessor->setState($assessmentTestSession);
     // -- single cardinality test.
     $result = $variableProcessor->process();
     $this->assertInstanceOf('qtism\\common\\datatypes\\Float', $result);
     $this->assertEquals(1470.7, $result->getValue());
     // The value in the state must be intact.
     $this->assertEquals(1337, $assessmentTestSession['Q01.var1']->getValue());
     // What if the indicated weight is not found?
     $variableExpr = $this->createComponentFromXml('<variable identifier="Q01.var1" weightIdentifier="weight2" />');
     $variableProcessor->setExpression($variableExpr);
     $result = $variableProcessor->process();
     $this->assertEquals(1337, $result->getValue());
     // -- multiple cardinality test.
     $assessmentTestSession['Q01.var2'] = new MultipleContainer(BaseType::FLOAT, array(new Float(10.1), new Float(12.1)));
     $variableExpr = $this->createComponentFromXml('<variable identifier="Q01.var2" weightIdentifier="weight1"/>');
     $variableProcessor->setExpression($variableExpr);
     $result = $variableProcessor->process();
     $this->assertEquals(11.11, $result[0]->getValue());
     $this->assertEquals(13.31, $result[1]->getValue());
     // The value in the state must be unchanged.
     $stateVal = $assessmentTestSession['Q01.var2'];
     $this->assertEquals(10.1, $stateVal[0]->getValue());
     $this->assertEquals(12.1, $stateVal[1]->getValue());
 }
 public function testMarshall()
 {
     $identifier = 'myAssessmentTest';
     $title = 'My Assessment Test';
     $toolName = 'QTIStateMachine';
     $toolVersion = '1.0b';
     $assessmentSections = new AssessmentSectionCollection();
     $assessmentSections[] = new AssessmentSection('myAssessmentSection', 'My Assessment Section', true);
     $testParts = new TestPartCollection();
     $testParts[] = new TestPart('myTestPart', $assessmentSections);
     $div = new Div();
     $div->setContent(new FlowCollection(array(new TextRun('Feedback!'))));
     $testFeedBacks = new TestFeedbackCollection();
     $testFeedBacks[] = new TestFeedback('myFeedback', 'myOutcome', new FlowStaticCollection(array($div)), 'A Feedback');
     $outcomeRules = new OutcomeRuleCollection();
     $outcomeRules[] = new SetOutcomeValue('myOutcome', new BaseValue(BaseType::BOOLEAN, true));
     $outcomeProcessing = new OutcomeProcessing($outcomeRules);
     $outcomeDeclarations = new OutcomeDeclarationCollection();
     $outcomeDeclarations[] = new OutcomeDeclaration('myOutcome', BaseType::BOOLEAN);
     $component = new AssessmentTest($identifier, $title, $testParts);
     $component->setToolName($toolName);
     $component->setToolVersion($toolVersion);
     $component->setTestFeedbacks($testFeedBacks);
     $component->setOutcomeProcessing($outcomeProcessing);
     $component->setOutcomeDeclarations($outcomeDeclarations);
     $marshaller = $this->getMarshallerFactory('2.1.0')->createMarshaller($component);
     $element = $marshaller->marshall($component);
     $this->assertInstanceOf('\\DOMElement', $element);
     $this->assertEquals('assessmentTest', $element->nodeName);
     $this->assertEquals($identifier, $element->getAttribute('identifier'));
     $this->assertEquals($title, $element->getAttribute('title'));
     $this->assertEquals($toolName, $element->getAttribute('toolName'));
     $this->assertEquals($toolVersion, $element->getAttribute('toolVersion'));
     // testParts
     $this->assertEquals(1, $element->getElementsByTagName('testPart')->length);
     $this->assertTrue($element === $element->getElementsByTagName('testPart')->item(0)->parentNode);
     // assessmentSections
     $testPart = $element->getElementsByTagName('testPart')->item(0);
     $this->assertEquals(1, $element->getElementsByTagName('assessmentSection')->length);
     $this->assertTrue($testPart === $element->getElementsByTagName('assessmentSection')->item(0)->parentNode);
     // outcomeDeclarations
     $this->assertEquals(1, $element->getElementsByTagName('outcomeDeclaration')->length);
     $this->assertTrue($element === $element->getElementsByTagName('outcomeDeclaration')->item(0)->parentNode);
     // testFeedbacks
     $this->assertEquals(1, $element->getElementsByTagName('testFeedback')->length);
     $this->assertTrue($element === $element->getElementsByTagName('testFeedback')->item(0)->parentNode);
     // outcomeProcessing
     $this->assertEquals(1, $element->getElementsByTagName('outcomeProcessing')->length);
     $this->assertTrue($element === $element->getElementsByTagName('outcomeProcessing')->item(0)->parentNode);
 }
 /**
  * Compile the RubricBlocRefs' contents into a separate rubric block PHP template.
  * 
  * @param AssessmentTest $assessmentTest The AssessmentTest object you want to compile the rubrickBlocks.
  */
 protected function compileRubricBlocks(AssessmentTest $assessmentTest)
 {
     $rubricBlockRefs = $assessmentTest->getComponentsByClassName('rubricBlockRef');
     $testService = taoQtiTest_models_classes_QtiTestService::singleton();
     $sourceDir = $testService->getQtiTestDir($this->getResource());
     foreach ($rubricBlockRefs as $rubricRef) {
         $rubricRefHref = $rubricRef->getHref();
         $cssScoper = $this->getCssScoper();
         $renderingEngine = $this->getRenderingEngine();
         $markupPostRenderer = $this->getMarkupPostRenderer();
         $publicCompiledDocDir = $this->getPublicDirectory();
         $privateCompiledDocDir = $this->getPrivateDirectory();
         // -- loading...
         common_Logger::t("Loading rubricBlock '" . $rubricRefHref . "'...");
         $rubricDoc = new XmlDocument();
         $rubricDoc->loadFromString($this->getPrivateDirectory()->read($rubricRefHref));
         common_Logger::t("rubricBlock '" . $rubricRefHref . "' successfully loaded.");
         // -- rendering...
         common_Logger::t("Rendering rubricBlock '" . $rubricRefHref . "'...");
         $pathinfo = pathinfo($rubricRefHref);
         $renderingFile = $pathinfo['filename'] . '.php';
         $rubric = $rubricDoc->getDocumentComponent();
         $rubricStylesheets = $rubric->getStylesheets();
         $stylesheets = new StylesheetCollection();
         // In any case, include the base QTI Stylesheet.
         $stylesheets->merge($rubricStylesheets);
         $rubric->setStylesheets($stylesheets);
         // -- If the rubricBlock has no id, give it a auto-generated one in order
         // to be sure that CSS rescoping procedure works fine (it needs at least an id
         // to target its scoping).
         if ($rubric->hasId() === false) {
             // Prepend 'tao' to the generated id because the CSS
             // ident token must begin by -|[a-zA-Z]
             $rubric->setId('tao' . uniqid());
         }
         // -- Copy eventual remote resources of the rubricBlock.
         $this->copyRemoteResources($rubric);
         $domRendering = $renderingEngine->render($rubric);
         $mainStringRendering = $markupPostRenderer->render($domRendering);
         // Prepend stylesheets rendering to the main rendering.
         $styleRendering = $renderingEngine->getStylesheets();
         $mainStringRendering = $styleRendering->ownerDocument->saveXML($styleRendering) . $mainStringRendering;
         foreach ($stylesheets as $rubricStylesheet) {
             $relPath = trim($this->getExtraPath(), '/');
             $relPath = (empty($relPath) ? '' : $relPath . DIRECTORY_SEPARATOR) . $rubricStylesheet->getHref();
             $sourceFile = $sourceDir->getFile($relPath);
             if (!$publicCompiledDocDir->has($relPath)) {
                 try {
                     $data = $sourceFile->read();
                     $tmpDir = \tao_helpers_File::createTempDir();
                     $tmpFile = $tmpDir . 'tmp.css';
                     file_put_contents($tmpFile, $data);
                     $scopedCss = $cssScoper->render($tmpFile, $rubric->getId());
                     unlink($tmpFile);
                     rmdir($tmpDir);
                     $publicCompiledDocDir->write($relPath, $scopedCss);
                 } catch (\InvalidArgumentException $e) {
                     common_Logger::e('Unable to copy file into public directory: ' . $relPath);
                 }
             }
         }
         // -- Replace the artificial 'tao://qti-directory' base path with a runtime call to the delivery time base path.
         $mainStringRendering = str_replace(TAOQTITEST_PLACEHOLDER_BASE_URI, '<?php echo $' . TAOQTITEST_BASE_PATH_NAME . '; ?>', $mainStringRendering);
         if (!$privateCompiledDocDir->has($renderingFile)) {
             try {
                 $privateCompiledDocDir->write($renderingFile, $mainStringRendering);
                 common_Logger::t("rubricBlockRef '" . $rubricRefHref . "' successfully rendered.");
             } catch (\InvalidArgumentException $e) {
                 common_Logger::e('Unable to copy file into public directory: ' . $renderingFile);
             }
         }
         // -- Clean up old rubric block and reference the new rubric block template.
         $privateCompiledDocDir->delete($rubricRefHref);
         $rubricRef->setHref('./' . $pathinfo['filename'] . '.php');
     }
 }
 /**
  * Append a QTI-SDK OutcomeRule object in an AssessmentTest's OutcomeProcessing.
  * 
  * In case of no OutcomeProcessing is set yet for the AssessmentTest $test object,
  * it will be automatically created, with the OutcomeRule $rule as its first
  * rule. Otherwise, the OutcomeRule $rule is simply appended to the existing
  * OutcomeProcessing object.
  * 
  * @param qtism\data\AssessmentTest $test A QTI-SDK AssessmentTest object.
  * @param qtism\data\rules\OutcomeRule A QTI-SDK OutcomeRule object.
  */
 private static function appendOutcomeRule(AssessmentTest $test, OutcomeRule $rule)
 {
     $outcomeProcessing = $test->getOutcomeProcessing();
     if ($outcomeProcessing === null) {
         $test->setOutcomeProcessing(new OutcomeProcessing(new OutcomeRuleCollection(array($rule))));
     } else {
         $outcomeProcessing->getOutcomeRules()[] = $rule;
     }
 }
 /**
  * Contains the logic of creating the Route of a brand new AssessmentTestSession object.
  * The resulting Route object will be injected in the created AssessmentTestSession.
  *
  * @param \qtism\data\AssessmentTest $test
  * @return \qtism\runtime\tests\Route A newly instantiated Route object.
  */
 protected function createRoute(AssessmentTest $test)
 {
     $routeStack = array();
     foreach ($test->getTestParts() as $testPart) {
         $assessmentSectionStack = array();
         foreach ($testPart->getAssessmentSections() as $assessmentSection) {
             $trail = array();
             $mark = array();
             array_push($trail, $assessmentSection);
             while (count($trail) > 0) {
                 $current = array_pop($trail);
                 if (!in_array($current, $mark, true) && $current instanceof AssessmentSection) {
                     // 1st pass on assessmentSection.
                     $currentAssessmentSection = $current;
                     array_push($assessmentSectionStack, $currentAssessmentSection);
                     array_push($mark, $current);
                     array_push($trail, $current);
                     foreach (array_reverse($current->getSectionParts()->getArrayCopy()) as $sectionPart) {
                         array_push($trail, $sectionPart);
                     }
                 } elseif (in_array($current, $mark, true)) {
                     // 2nd pass on assessmentSection.
                     // Pop N routeItems where N is the children count of $current.
                     $poppedRoutes = array();
                     for ($i = 0; $i < count($current->getSectionParts()); $i++) {
                         $poppedRoutes[] = array_pop($routeStack);
                     }
                     $selection = new BasicSelection($current, new SelectableRouteCollection(array_reverse($poppedRoutes)));
                     $selectedRoutes = $selection->select();
                     // Shuffling can be applied on selected routes.
                     // $route will contain the final result of the selection + ordering.
                     $ordering = new BasicOrdering($current, $selectedRoutes);
                     $selectedRoutes = $ordering->order();
                     $route = new SelectableRoute($current->isFixed(), $current->isRequired(), $current->isVisible(), $current->mustKeepTogether());
                     foreach ($selectedRoutes as $r) {
                         $route->appendRoute($r);
                     }
                     // Add to the last item of the selection the branch rules of the AssessmentSection/testPart
                     // on which the selection is applied... Only if the route contains something (empty assessmentSection edge case).
                     if ($route->count() > 0) {
                         $route->getLastRouteItem()->addBranchRules($current->getBranchRules());
                         // Do the same as for branch rules for pre conditions, except that they must be
                         // attached on the first item of the route.
                         $route->getFirstRouteItem()->addPreConditions($current->getPreConditions());
                     }
                     array_push($routeStack, $route);
                     array_pop($assessmentSectionStack);
                 } elseif ($current instanceof AssessmentItemRef) {
                     // leaf node.
                     $route = new SelectableRoute($current->isFixed(), $current->isRequired());
                     $route->addRouteItem($current, new AssessmentSectionCollection($assessmentSectionStack), $testPart, $test);
                     array_push($routeStack, $route);
                 }
             }
         }
     }
     $finalRoutes = $routeStack;
     $route = new SelectableRoute();
     foreach ($finalRoutes as $finalRoute) {
         $route->appendRoute($finalRoute);
     }
     return $route;
 }
 /**
  * Create a new instance of XmlCompactDocument from an XmlAssessmentTestDocument.
  *
  * @param XmlDocument $xmlAssessmentTestDocument An XmlAssessmentTestDocument object you want to store as a compact XML file.
  * @return XmlCompactDocument An XmlCompactAssessmentTestDocument object.
  * @throws XmlStorageException If an error occurs while transforming the XmlAssessmentTestDocument object into an XmlCompactAssessmentTestDocument object.
  */
 public static function createFromXmlAssessmentTestDocument(XmlDocument $xmlAssessmentTestDocument, FileResolver $itemResolver = null)
 {
     $compactAssessmentTest = new XmlCompactDocument();
     $identifier = $xmlAssessmentTestDocument->getDocumentComponent()->getIdentifier();
     $title = $xmlAssessmentTestDocument->getDocumentComponent()->getTitle();
     $assessmentTest = new AssessmentTest($identifier, $title);
     $assessmentTest->setOutcomeDeclarations($xmlAssessmentTestDocument->getDocumentComponent()->getOutcomeDeclarations());
     $assessmentTest->setOutcomeProcessing($xmlAssessmentTestDocument->getDocumentComponent()->getOutcomeProcessing());
     $assessmentTest->setTestFeedbacks($xmlAssessmentTestDocument->getDocumentComponent()->getTestFeedbacks());
     $assessmentTest->setTestParts($xmlAssessmentTestDocument->getDocumentComponent()->getTestParts());
     $assessmentTest->setTimeLimits($xmlAssessmentTestDocument->getDocumentComponent()->getTimeLimits());
     $assessmentTest->setToolName($xmlAssessmentTestDocument->getDocumentComponent()->getToolName());
     $assessmentTest->setToolVersion($xmlAssessmentTestDocument->getDocumentComponent()->getToolVersion());
     // File resolution.
     $sectionResolver = new LocalFileResolver($xmlAssessmentTestDocument->getUrl());
     if (is_null($itemResolver) === true) {
         $itemResolver = new LocalFileResolver($xmlAssessmentTestDocument->getUrl());
     } else {
         $itemResolver->setBasePath($xmlAssessmentTestDocument->getUrl());
     }
     // It simply consists of replacing assessmentItemRef and assessmentSectionRef elements.
     $trail = array();
     // trailEntry[0] = a component, trailEntry[1] = from where we are coming (parent).
     $mark = array();
     $root = $xmlAssessmentTestDocument->getDocumentComponent();
     array_push($trail, array($root, $root));
     while (count($trail > 0)) {
         $trailer = array_pop($trail);
         $component = $trailer[0];
         $previous = $trailer[1];
         if (!in_array($component, $mark) && count($component->getComponents()) > 0) {
             // First pass on a hierarchical node... go deeper in the n-ary tree.
             array_push($mark, $component);
             // We want to go back on this component.
             array_push($trail, $trailer);
             // Prepare further exploration.
             foreach ($component->getComponents()->getArrayCopy() as $comp) {
                 array_push($trail, array($comp, $component));
             }
         } else {
             if (in_array($component, $mark) || count($component->getComponents()) === 0) {
                 // Second pass on a hierarchical node (we are bubbling up accross the n-ary tree)
                 // OR
                 // Leaf node
                 if ($component instanceof AssessmentItemRef) {
                     // Transform the ref in an compact extended ref.
                     $compactRef = ExtendedAssessmentItemRef::createFromAssessmentItemRef($component);
                     // find the old one and replace it.
                     $previousParts = $previous->getSectionParts();
                     foreach ($previousParts as $k => $previousPart) {
                         if ($previousParts[$k] === $component) {
                             // If the previous processed component is an XmlAssessmentSectionDocument,
                             // it means that the given baseUri must be adapted.
                             $baseUri = $xmlAssessmentTestDocument->getUrl();
                             if ($component instanceof XmlDocument && $component->getDocumentComponent() instanceof AssessmentSection) {
                                 $baseUri = $component->getUrl();
                             }
                             $itemResolver->setBasePath($baseUri);
                             self::resolveAssessmentItemRef($compactRef, $itemResolver);
                             $previousParts->replace($component, $compactRef);
                             break;
                         }
                     }
                 } else {
                     if ($component instanceof AssessmentSectionRef) {
                         // We follow the unreferenced AssessmentSection as if it was
                         // the 1st pass.
                         $assessmentSection = self::resolveAssessmentSectionRef($component, $sectionResolver);
                         $previousParts = $previous->getSectionParts();
                         foreach ($previousParts as $k => $previousPart) {
                             if ($previousParts[$k] === $component) {
                                 $previousParts->replace($component, $assessmentSection);
                                 break;
                             }
                         }
                         array_push($trail, array($assessmentSection, $previous));
                     } else {
                         if ($component instanceof AssessmentSection) {
                             $assessmentSection = ExtendedAssessmentSection::createFromAssessmentSection($component);
                             $previousParts = $previous instanceof TestPart ? $previous->getAssessmentSections() : $previous->getSectionParts();
                             foreach ($previousParts as $k => $previousPart) {
                                 if ($previousParts[$k] === $component) {
                                     $previousParts->replace($component, $assessmentSection);
                                     break;
                                 }
                             }
                         } else {
                             if ($component === $root) {
                                 // 2nd pass on the root, we have to stop.
                                 $compactAssessmentTest->setDocumentComponent($assessmentTest);
                                 return $compactAssessmentTest;
                             }
                         }
                     }
                 }
             }
         }
     }
 }
 /**
  * Compile the RubricBlocRefs' contents into a separate rubric block PHP template.
  * 
  * @param AssessmentTest $assessmentTest The AssessmentTest object you want to compile the rubrickBlocks.
  */
 protected function compileRubricBlocks(AssessmentTest $assessmentTest)
 {
     $rubricBlockRefs = $assessmentTest->getComponentsByClassName('rubricBlockRef');
     foreach ($rubricBlockRefs as $rubricRef) {
         $rubricRefHref = $rubricRef->getHref();
         $cssScoper = $this->getCssScoper();
         $renderingEngine = $this->getRenderingEngine();
         $markupPostRenderer = $this->getMarkupPostRenderer();
         $compiledDocDir = $this->getPrivateDirectory()->getPath();
         $publicCompiledDocDir = $this->getPublicDirectory()->getPath();
         // -- loading...
         common_Logger::t("Loading rubricBlock '" . $rubricRefHref . "'...");
         $rubricDoc = new XmlDocument();
         $rubricDoc->load($compiledDocDir . $rubricRefHref);
         common_Logger::t("rubricBlock '" . $rubricRefHref . "' successfully loaded.");
         // -- rendering...
         common_Logger::t("Rendering rubricBlock '" . $rubricRefHref . "'...");
         $pathinfo = pathinfo($rubricRefHref);
         $renderingFile = $compiledDocDir . $pathinfo['filename'] . '.php';
         $rubric = $rubricDoc->getDocumentComponent();
         $rubricStylesheets = $rubric->getStylesheets();
         $stylesheets = new StylesheetCollection();
         // In any case, include the base QTI Stylesheet.
         $stylesheets->merge($rubricStylesheets);
         $rubric->setStylesheets($stylesheets);
         // -- If the rubricBlock has no id, give it a auto-generated one in order
         // to be sure that CSS rescoping procedure works fine (it needs at least an id
         // to target its scoping).
         if ($rubric->hasId() === false) {
             // Prepend 'tao' to the generated id because the CSS
             // ident token must begin by -|[a-zA-Z]
             $rubric->setId('tao' . uniqid());
         }
         // -- Copy eventual remote resources of the rubricBlock.
         $this->copyRemoteResources($rubric);
         $domRendering = $renderingEngine->render($rubric);
         $mainStringRendering = $markupPostRenderer->render($domRendering);
         // Prepend stylesheets rendering to the main rendering.
         $styleRendering = $renderingEngine->getStylesheets();
         $mainStringRendering = $styleRendering->ownerDocument->saveXML($styleRendering) . $mainStringRendering;
         foreach ($stylesheets as $rubricStylesheet) {
             $stylesheetPath = taoQtiTest_helpers_Utils::storedQtiResourcePath($compiledDocDir . ltrim($this->getExtraPath(), '/'), $rubricStylesheet->getHref());
             file_put_contents($stylesheetPath, $cssScoper->render($stylesheetPath, $rubric->getId()));
         }
         // -- Replace the artificial 'tao://qti-directory' base path with a runtime call to the delivery time base path.
         $mainStringRendering = str_replace(TAOQTITEST_PLACEHOLDER_BASE_URI, '<?php echo $' . TAOQTITEST_BASE_PATH_NAME . '; ?>', $mainStringRendering);
         file_put_contents($renderingFile, $mainStringRendering);
         common_Logger::t("rubricBlockRef '" . $rubricRefHref . "' successfully rendered.");
         // -- Clean up old rubric block and reference the new rubric block template.
         unlink($compiledDocDir . $rubricRefHref);
         $rubricRef->setHref('./' . $pathinfo['filename'] . '.php');
     }
 }