/** * 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'); } }