public function testSeparateStylesheetTwo() { // The loaded component is still a rubricBlock but this // time with two (YES, TWO!) stylesheets. $doc = new XmlDocument('2.1'); $doc->load(self::samplesDir() . 'rendering/rubricblock_3.xml'); $this->assertEquals(2, count($doc->getDocumentComponent()->getStylesheets())); $renderingEngine = new XhtmlRenderingEngine(); $renderingEngine->setStylesheetPolicy(XhtmlRenderingEngine::STYLESHEET_SEPARATE); $rendering = $renderingEngine->render($doc->getDocumentComponent()); $linkElts = $rendering->getElementsByTagName('link'); $this->assertEquals(0, $linkElts->length); $linksFragment = $renderingEngine->getStylesheets(); $this->assertInstanceOf('\\DOMDocumentFragment', $linksFragment); $this->assertEquals(2, $linksFragment->childNodes->length); // Test first <link> element. $linkElt = $linksFragment->childNodes->item(0); $this->assertEquals('link', $linkElt->localName); $this->assertEquals('style1.css', $linkElt->getAttribute('href')); $this->assertEquals('text/css', $linkElt->getAttribute('type')); $this->assertEquals('screen', $linkElt->getAttribute('media')); $this->assertEquals('\\0_ !HOURRAY! _0/', $linkElt->getAttribute('title')); // Test second <link> element. $linkElt = $linksFragment->childNodes->item(1); $this->assertEquals('link', $linkElt->localName); $this->assertEquals('style2.css', $linkElt->getAttribute('href')); $this->assertEquals('text/css', $linkElt->getAttribute('type')); $this->assertEquals('screen', $linkElt->getAttribute('media')); $this->assertEquals('0/*\\0 (Jedi duel)', $linkElt->getAttribute('title')); }
/** * Converts the test from the document to an array * @return array the test data as array * @throws taoQtiTest_models_classes_QtiTestConverterException */ public function toArray() { try { return $this->componentToArray($this->doc->getDocumentComponent()); } catch (ReflectionException $re) { common_Logger::e($re->getMessage()); common_Logger::d($re->getTraceAsString()); throw new taoQtiTest_models_classes_QtiTestConverterException('Unable to convert the QTI Test to json: ' . $re->getMessage()); } }
public function testItemSessionControls() { $doc = new XmlDocument('2.1'); $doc->load(self::samplesDir() . 'custom/runtime/routeitem_itemsessioncontrols.xml'); // Q01. $q01 = $doc->getDocumentComponent()->getComponentByIdentifier('Q01'); $this->assertInstanceOf('qtism\\data\\AssessmentItemRef', $q01); $this->assertEquals(2, $q01->getItemSessionControl()->getMaxAttempts()); // P02. $p02 = $doc->getDocumentComponent()->getComponentByIdentifier('P02'); $this->assertInstanceOf('qtism\\data\\TestPart', $p02); $this->assertEquals(4, $p02->getItemSessionControl()->getMaxAttempts()); }
/** * @depends testLoadAndResolveXIncludeSameBase */ public function testLoadAndResolveXIncludeDifferentBase() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'custom/items/xinclude/xinclude_ns_in_tag_subfolder.xml', true); $doc->xInclude(); $includes = $doc->getDocumentComponent()->getComponentsByClassName('include'); $this->assertEquals(0, count($includes)); // And we should find an img component then! $imgs = $doc->getDocumentComponent()->getComponentsByClassName('img'); $this->assertEquals(1, count($imgs)); // Check that xml:base was appropriately resolved. In this case, // no content for xml:base because 'xinclude_ns_in_tag_content1.xml' is in the // same directory as the main xml file. $this->assertEquals('subfolder/', $imgs[0]->getXmlBase()); }
public function parse($xmlString, $validate = true) { // TODO: Remove this, and move it higher up LogService::flush(); $xmlDocument = new XmlDocument(); if ($validate === false) { LogService::log('QTI pre-validation is turned off, some invalid attributes might be stripped from XML content upon conversion'); } $xmlDocument->loadFromString($xmlString, $validate); /** @var AssessmentItem $assessmentItem */ $assessmentTest = $xmlDocument->getDocumentComponent(); if (!$assessmentTest instanceof AssessmentTest) { throw new MappingException('XML is not a valid <assessmentItem> document'); } // Ignore `testPart` and `assessmentSection`. Grab every item references and merge in array $itemReferences = []; foreach ($assessmentTest->getComponentsByClassName('assessmentItemRef', true) as $assessmentItemRef) { $itemReferences[] = $assessmentItemRef->getIdentifier(); } LogService::log('Support for mapping is very limited. Elements such `testPart`, `assessmentSections`, `seclection`, `rubricBlock`, ' . 'etc are ignored. Please see developer docs for more details'); $data = new activity_data(); $data->set_items($itemReferences); $activity = new activity($assessmentTest->getIdentifier(), $data); // Flush out all the error messages stored in this static class, also ensure they are unique $messages = array_values(array_unique(LogService::flush())); return [$activity, $messages]; }
public function testAssigningScoresAndCorrectResponses() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'custom/items/template_processing.xml'); $session = new AssessmentItemSession($doc->getDocumentComponent()); $itemSessionControl = new ItemSessionControl(); $itemSessionControl->setMaxAttempts(0); $session->setItemSessionControl($itemSessionControl); $session->beginItemSession(); // Check that the templateProcessing was correctly processed. $this->assertEquals('ChoiceA', $session->getVariable('RESPONSE')->getCorrectResponse()->getValue()); $this->assertEquals(1.0, $session['GOODSCORE']->getValue()); $this->assertEquals(0.0, $session['WRONGSCORE']->getValue()); // Check that it really works... // With a correct response. $session->beginAttempt(); $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA')))); $session->endAttempt($responses); $this->assertEquals(1.0, $session['SCORE']->getValue()); // With an incorrect response. $session->beginAttempt(); $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceB')))); $session->endAttempt($responses); $this->assertEquals(0.0, $session['SCORE']->getValue()); }
public function testWrite() { $uri = self::samplesDir() . 'custom/standalone_assessmentsection.xml'; $doc = new XmlDocument(); $doc->load($uri); $assessmentSection = $doc->getDocumentComponent(); // Write the file. $uri = tempnam('/tmp', 'qsm'); $doc->save($uri); $this->assertTrue(file_exists($uri)); // Reload it. $doc->load($uri); $this->assertInstanceOf('qtism\\data\\AssessmentSection', $doc->getDocumentComponent()); // Retest. $this->testLoad($doc->getDocumentComponent()); unlink($uri); }
public function testGetShuffledChoiceIdentifierAtInvalidShuffledChoiceIndex() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'ims/items/2_1/choice_fixed.xml'); $session = new AssessmentItemSession($doc->getDocumentComponent()); $session->beginItemSession(); $this->setExpectedException('\\OutOfBoundsException', 'No identifier at index 1337.'); $session->getShuffledChoiceIdentifierAt(0, 1337); }
public function testTimeLimits() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'custom/runtime/timelimits.xml'); $testPart = $doc->getDocumentComponent()->getComponentByIdentifier('testPartId'); $this->assertTrue($testPart->hasTimeLimits()); $timeLimits = $testPart->getTimeLimits(); $this->assertTrue($timeLimits->getMinTime()->equals(new Duration('PT60S'))); $this->assertTrue($timeLimits->getMaxTime()->equals(new Duration('PT120S'))); $this->assertTrue($timeLimits->doesAllowLateSubmission()); }
public function testBasicSelection() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'custom/runtime/selection_and_ordering.xml'); $testPart = $doc->getDocumentComponent()->getComponentByIdentifier('testPart'); $this->assertEquals('testPart', $testPart->getIdentifier()); $s01 = $doc->getDocumentComponent()->getComponentByIdentifier('S01', true); $this->assertEquals('S01', $s01->getIdentifier()); // Prepare route selection of S01A. $s01a = $doc->getDocumentComponent()->getComponentByIdentifier('S01A', true); $this->assertEquals('S01A', $s01a->getIdentifier()); $s01aRoute = new SelectableRoute(); foreach ($s01a->getSectionParts() as $sectionPart) { $s01aRoute->addRouteItem($sectionPart, $s01a, $testPart, $doc->getDocumentComponent()); } // Prepare route selection of S01B. $s01b = $doc->getDocumentComponent()->getComponentByIdentifier('S01B', true); $this->assertEquals('S01B', $s01b->getIdentifier()); $s01bRoute = new SelectableRoute(); foreach ($s01b->getSectionParts() as $sectionPart) { $s01bRoute->addRouteItem($sectionPart, $s01b, $testPart, $doc->getDocumentComponent()); } $selection = new BasicSelection($s01, new SelectableRouteCollection(array($s01aRoute, $s01bRoute))); $selectedRoutes = $selection->select(); $selectedRoute = new SelectableRoute(); foreach ($selectedRoutes as $r) { $selectedRoute->appendRoute($r); } $routeCheck1 = self::isRouteCorrect($selectedRoute, array('Q1', 'Q2', 'Q3')); $routeCheck2 = self::isRouteCorrect($selectedRoute, array('Q4', 'Q5', 'Q6')); $this->assertFalse($routeCheck1 === true && $routeCheck2 === true); $this->assertTrue($routeCheck1 === true || $routeCheck2 === true); }
function testAssessmentTests(array $files, $validate = false) { $loaded = 0; $totalSpent = 0; foreach ($files as $f) { $start = microtime(); $testDoc = new XmlDocument(); $testDoc->load($f, $validate); $end = microtime(); $spent = spentTime($start, $end); $totalSpent += $spent; output("Test '" . pathinfo($f, PATHINFO_BASENAME) . "' loaded in " . sprintf("%.8f", $spent) . " seconds."); $partCount = count($testDoc->getDocumentComponent()->getComponentsByClassName('testPart')); $sectionCount = count($testDoc->getDocumentComponent()->getComponentsByClassName('assessmentSection')); $itemCount = count($testDoc->getDocumentComponent()->getComponentsByClassName('assessmentItemRef')); outputDescription("{$partCount} testPart(s), {$sectionCount} assessmentSection(s), {$itemCount} assessmentItemRef(s)"); outputDescription("Memory usage is " . memory_get_usage() / pow(1024, 2) . " MB"); output(''); $loaded++; } outputAverage($totalSpent / $loaded); }
/** * Item's ResponseProcessing. * * @param core_kernel_classes_Resource $item The Item you want to apply ResponseProcessing. * @throws \RuntimeException If an error occurs while processing responses or transmitting results */ protected function processResponses(core_kernel_classes_Resource $item) { $jsonPayload = taoQtiCommon_helpers_Utils::readJsonPayload(); try { $qtiXmlFilePath = QtiFile::getQtiFilePath($item); $qtiXmlDoc = new XmlDocument(); $qtiXmlDoc->load($qtiXmlFilePath); } catch (StorageException $e) { $msg = "An error occurred while loading QTI-XML file at expected location '{$qtiXmlFilePath}'."; common_Logger::e($e->getPrevious()->getMessage()); throw new \RuntimeException($msg, 0, $e); } $itemSession = new AssessmentItemSession($qtiXmlDoc->getDocumentComponent(), new SessionManager()); $itemSession->beginItemSession(); $variables = array(); $filler = new taoQtiCommon_helpers_PciVariableFiller($qtiXmlDoc->getDocumentComponent()); // Convert client-side data as QtiSm Runtime Variables. foreach ($jsonPayload as $id => $response) { try { $var = $filler->fill($id, $response); // Do not take into account QTI Files at preview time. // Simply delete the created file. if (taoQtiCommon_helpers_Utils::isQtiFile($var, false) === true) { $fileManager = taoQtiCommon_helpers_Utils::getFileDatatypeManager(); $fileManager->delete($var->getValue()); } else { $variables[] = $var; } } catch (OutOfRangeException $e) { // A variable value could not be converted, ignore it. // Developer's note: QTI Pairs with a single identifier (missing second identifier of the pair) are transmitted as an array of length 1, // this might cause problem. Such "broken" pairs are simply ignored. common_Logger::d("Client-side value for variable '{$id}' is ignored due to data malformation."); } catch (OutOfBoundsException $e) { // No such identifier found in item. common_Logger::d("The variable with identifier '{$id}' is not declared in the item definition."); } } try { $itemSession->beginAttempt(); $itemSession->endAttempt(new State($variables)); // Return the item session state to the client-side. echo json_encode(array('success' => true, 'displayFeedback' => true, 'itemSession' => self::buildOutcomeResponse($itemSession))); } catch (AssessmentItemSessionException $e) { $msg = "An error occurred while processing the responses."; throw new \RuntimeException($msg, 0, $e); } catch (taoQtiCommon_helpers_ResultTransmissionException $e) { $msg = "An error occurred while transmitting a result to the target Result Server."; throw new \RuntimeException($msg, 0, $e); } }
public function testLoadMatchCorrect() { $xml = new XmlDocument('2.1'); $xml->load(self::getTemplatesPath() . '2_1/match_correct.xml'); $this->assertInstanceOf('qtism\\data\\processing\\ResponseProcessing', $xml->getDocumentComponent()); $this->assertFalse($xml->getDocumentComponent()->hasTemplateLocation()); $this->assertFalse($xml->getDocumentComponent()->hasTemplate()); $responseRules = $xml->getDocumentComponent()->getResponseRules(); $this->assertEquals(1, count($responseRules)); $responseCondition = $responseRules[0]; $this->assertInstanceOf('qtism\\data\\rules\\ResponseCondition', $responseCondition); $responseIf = $responseCondition->getResponseIf(); $match = $responseIf->getExpression(); $this->assertInstanceOf('qtism\\data\\expressions\\operators\\Match', $match); $matchExpressions = $match->getExpressions(); $this->assertEquals(2, count($matchExpressions)); $variable = $matchExpressions[0]; $this->assertInstanceOf('qtism\\data\\expressions\\Variable', $variable); $this->assertEquals('RESPONSE', $variable->getIdentifier()); $correct = $matchExpressions[1]; $this->assertInstanceOf('qtism\\data\\expressions\\Correct', $correct); $this->assertEquals('RESPONSE', $correct->getIdentifier()); // To be continued... }
public function testModerateXmlBase() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'rendering/xmlbase_2.xml'); $div = $doc->getDocumentComponent(); $this->assertInstanceOf('qtism\\data\\content\\xhtml\\text\\Div', $div); $this->assertFalse($div->hasXmlBase()); $this->assertEquals('', $div->getXmlBase()); $subDivs = $div->getComponentsByClassName('div'); $this->assertEquals(2, count($subDivs)); $this->assertTrue($subDivs[0]->hasXmlBase()); $this->assertEquals('http://www.qtism-project.org/farm/', $subDivs[0]->getXmlBase()); $this->assertTrue($subDivs[1]->hasXmlBase()); $this->assertEquals('http://www.qtism-project.org/birds/', $subDivs[1]->getXmlBase()); }
public function parse($xmlString, $validate = true) { // TODO: Remove this, and move it higher up LogService::flush(); $xmlDocument = new XmlDocument(); if ($validate === false) { LogService::log('QTI pre-validation is turned off, some invalid attributes might be stripped from XML content upon conversion'); } $xmlDocument->loadFromString($xmlString, $validate); /** @var AssessmentItem $assessmentItem */ $assessmentItem = $xmlDocument->getDocumentComponent(); if (!$assessmentItem instanceof AssessmentItem) { throw new MappingException('XML is not a valid <assessmentItem> document'); } return $this->parseWithAssessmentItemComponent($assessmentItem); }
protected function convertToAssessmentItem(array $data) { list($xml, $manifest) = Converter::convertLearnosityToQtiItem($data); // Assert the XML string is formed and not empty // Also, assert manifest is in form of array, regardless it was empty or not $this->assertTrue(is_string($xml) && !empty($xml)); $this->assertTrue(is_array($manifest)); $document = new XmlDocument(); $document->loadFromString($xml); /** @var AssessmentItem $assessmentItem */ $assessmentItem = $document->getDocumentComponent(); // Basic assert on <assessmentItem> object $this->assertNotNull($assessmentItem); $this->assertTrue($assessmentItem instanceof AssessmentItem); return $assessmentItem; }
public function testTemplateVariableDefault() { // This test aims at testing whether template variables // are correctly instantiated as part of the item session and // they can be used in response processing. $doc = new XmlDocument('2.1.0'); $doc->load(self::samplesDir() . 'custom/items/template_declaration_default.xml'); $itemSession = new AssessmentItemSession($doc->getDocumentComponent()); $itemSessionControl = new ItemSessionControl(); $itemSessionControl->setMaxAttempts(0); $itemSession->setItemSessionControl($itemSessionControl); $itemSession->beginItemSession(); $this->assertTrue($itemSession['WRONGSCORE']->equals(new Float(0.0))); $this->assertTrue($itemSession['GOODSCORE']->equals(new Float(1.0))); // 1st attempt to get 'GOODSCORE' as 'SCORE'. $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new Identifier('ChoiceA')))); $itemSession->beginAttempt(); $itemSession->endAttempt($responses); $this->assertTrue($itemSession['SCORE']->equals($itemSession['GOODSCORE'])); // 2nd attempt to get 'WRONGSCORE' as 'SCORE'. $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new Identifier('ChoiceB')))); $itemSession->beginAttempt(); $itemSession->endAttempt($responses); $this->assertTrue($itemSession['SCORE']->equals($itemSession['WRONGSCORE'])); }
/** * Item's ResponseProcessing. * * @param core_kernel_file_File $itemPath The Item file resource you want to apply ResponseProcessing. * @throws RuntimeException If an error occurs while processing responses or transmitting results */ protected function processResponses(core_kernel_classes_Resource $item) { $jsonPayload = taoQtiCommon_helpers_Utils::readJsonPayload(); try { $qtiXmlFileContent = QtiFile::getQtiFileContent($item); $qtiXmlDoc = new XmlDocument(); $qtiXmlDoc->loadFromString($qtiXmlFileContent); } catch (StorageException $e) { $msg = "An error occured while loading QTI-XML file at expected location '{$qtiXmlFilePath}'."; throw new \RuntimeException($msg, 0, $e); } $itemSession = new AssessmentItemSession($qtiXmlDoc->getDocumentComponent(), new SessionManager()); $itemSession->beginItemSession(); $variables = array(); // Convert client-side data as QtiSm Runtime Variables. foreach ($jsonPayload as $identifier => $response) { $filler = new taoQtiCommon_helpers_PciVariableFiller($qtiXmlDoc->getDocumentComponent()); try { $var = $filler->fill($identifier, $response); // Do not take into account QTI File placeholders. if (taoQtiCommon_helpers_Utils::isQtiFilePlaceHolder($var) === false) { $variables[] = $var; } } catch (\OutOfRangeException $e) { // A variable value could not be converted, ignore it. // Developer's note: QTI Pairs with a single identifier (missing second identifier of the pair) are transmitted as an array of length 1, // this might cause problem. Such "broken" pairs are simply ignored. common_Logger::d("Client-side value for variable '{$identifier}' is ignored due to data malformation."); } catch (\OutOfBoundsException $e) { // The response identifier does not match any response declaration. common_Logger::d("Uknown item variable declaration '{$identifier}."); } } try { $itemSession->beginAttempt(); $itemSession->endAttempt(new State($variables)); // Transmit results to the Result Server. $this->transmitResults($item, $itemSession); // Return the item session state to the client-side. echo json_encode(array('success' => true, 'displayFeedback' => true, 'itemSession' => self::buildOutcomeResponse($itemSession), 'feedbacks' => $this->getFeedbacks($itemSession))); } catch (AssessmentItemSessionException $e) { $msg = "An error occured while processing the responses."; throw new \RuntimeException($msg, 0, $e); } catch (taoQtiCommon_helpers_ResultTransmissionException $e) { $msg = "An error occured while transmitting variable '{$identifier}' to the target Result Server."; throw new \RuntimeException($msg, 0, $e); } }
/** * 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'); } }
/** * Assign items to a QTI test. * @param XmlDocument $doc * @param array $items * @return type * @throws taoQtiTest_models_classes_QtiTestServiceException */ private function setItemsToDoc(XmlDocument $doc, array $items, $sectionIndex = 0) { $sections = $doc->getDocumentComponent()->getComponentsByClassName('assessmentSection'); if (!isset($sections[$sectionIndex])) { throw new taoQtiTest_models_classes_QtiTestServiceException('No section found in test at index : ' . $sectionIndex, taoQtiTest_models_classes_QtiTestServiceException::TEST_READ_ERROR); } $section = $sections[$sectionIndex]; $itemContentProperty = new core_kernel_classes_Property(TAO_ITEM_CONTENT_PROPERTY); $itemRefs = new SectionPartCollection(); $itemRefIdentifiers = array(); foreach ($items as $itemResource) { $itemContent = new core_kernel_file_File($itemResource->getUniquePropertyValue($itemContentProperty)); $itemDoc = new XmlDocument(); try { $itemDoc->load($itemContent->getAbsolutePath()); } catch (StorageException $e) { // We consider the item not compliant with QTI, let's try the next one. continue; } $itemRefIdentifier = $itemDoc->getDocumentComponent()->getIdentifier(); //enable more than one reference if (array_key_exists($itemRefIdentifier, $itemRefIdentifiers)) { $itemRefIdentifiers[$itemRefIdentifier] += 1; $itemRefIdentifier .= '-' . $itemRefIdentifiers[$itemRefIdentifier]; } else { $itemRefIdentifiers[$itemRefIdentifier] = 0; } $itemRefs[] = new AssessmentItemRef($itemRefIdentifier, $itemResource->getUri()); } $section->setSectionParts($itemRefs); return count($itemRefs); }
/** * * @dataProvider metaProvider * @param string $testFile * @param array $expectedMeta */ public function testTestMeta($testFile, $expectedMeta) { $xml = new XmlDocument(); $xml->load($testFile); $this->assertEquals($expectedMeta, taoQtiTest_helpers_TestCompilerUtils::testMeta($xml->getDocumentComponent())); }
/** * Run the rendering behaviour related to the "XHTML" flavour. * * @param \qtism\data\storage\xml\XmlDocument $doc The QTI XML document to be rendered. * @param \qtism\runtime\rendering\markup\xhtml\XhtmlRenderingEngine $renderer * @return string The raw rendering data. */ private function runXhtml(XmlDocument $doc, XhtmlRenderingEngine $renderer) { $arguments = $this->getArguments(); $profile = $arguments['flavour']; $xml = $renderer->render($doc->getDocumentComponent()); $header = ''; $footer = ''; $indent = ''; $nl = ''; if ($arguments['format'] === true) { $xml->formatOutput = true; $indent .= " "; $nl .= "\n"; } if ($arguments['document'] === true) { $rootComponent = $doc->getDocumentComponent(); $title = ''; if ($rootComponent->getQtiClassName() === 'assessmentItem') { $title = XmlUtils::escapeXmlSpecialChars($rootComponent->getTitle()); } $header .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; $header .= "<html>{$nl}"; $header .= "{$indent}<head>{$nl}"; $header .= "{$indent}{$indent}<meta charset=\"utf-8\">{$nl}"; if (empty($title) !== false) { $header .= "{$indent}{$indent}<title>" . $title . "</title>{$nl}"; } $header .= "{$indent}{$indent}" . $renderer->getStylesheets()->ownerDocument->saveXML($renderer->getStylesheets()); $header .= "{$indent}</head>{$nl}"; $header .= "{$indent}<body>{$nl}"; $footer = "{$indent}</body>{$nl}"; $footer .= "</html>\n"; } $body = $xml->saveXml($xml->documentElement) . "{$nl}"; // Indent body... $indentBody = ''; if ($arguments['document'] === null) { $indent = ''; } foreach (preg_split('/\\n|\\r/u', $body, -1, PREG_SPLIT_NO_EMPTY) as $bodyLine) { // do stuff with $line $indentBody .= "{$indent}{$indent}{$bodyLine}{$nl}"; } $body = $indentBody; return "{$header}{$indentBody}{$footer}"; }
public function testAppendNumberCorrectOutcomeProcessing() { $doc = new XmlDocument(); $doc->load(self::samplesDir() . 'categories.xml'); TestCategoryRulesUtils::appendNumberCorrectOutcomeProcessing($doc->getDocumentComponent(), 'math', 'MATH' . TestCategoryRulesUtils::NUMBER_CORRECT_SUFFIX); $this->assertInstanceOf('qtism\\data\\processing\\OutcomeProcessing', $doc->getDocumentComponent()->getOutcomeProcessing()); $outcomeRules = $doc->getDocumentComponent()->getOutcomeProcessing()->getOutcomeRules(); $this->assertCount(1, $outcomeRules); $this->assertInstanceOf('qtism\\data\\rules\\setOutcomeValue', $outcomeRules[0]); $this->assertEquals('MATH' . TestCategoryRulesUtils::NUMBER_CORRECT_SUFFIX, $outcomeRules[0]->getIdentifier()); $this->assertInstanceOF('qtism\\data\\expressions\\NumberCorrect', $outcomeRules[0]->getExpression()); $this->assertEquals(array('math'), $outcomeRules[0]->getExpression()->getIncludeCategories()->getArrayCopy()); // If a second call to TestCategoryRulesUtils::appendNumberCorrectOutcomeProcessing occurs for a variable wich // is already targeted by a setOutcomeValue rule, no new outcome rule should appear to avoid duplicates. TestCategoryRulesUtils::appendNumberCorrectOutcomeProcessing($doc->getDocumentComponent(), 'math', 'MATH' . TestCategoryRulesUtils::NUMBER_CORRECT_SUFFIX); $outcomeRules = $doc->getDocumentComponent()->getOutcomeProcessing()->getOutcomeRules(); $this->assertCount(1, $outcomeRules); }
public function testLoadInteractionMixSaschsen() { $xmlDoc = new XmlDocument('2.1'); $xmlDoc->load(self::samplesDir() . 'ims/tests/interaction_mix_sachsen/interaction_mix_sachsen.xml'); $phpDoc = new PhpDocument(); $phpDoc->setDocumentComponent($xmlDoc->getDocumentComponent()); $file = tempnam('/tmp', 'qsm'); $phpDoc->save($file); $phpDoc = new PhpDocument(); $phpDoc->load($file); $this->assertEquals('InteractionMixSachsen_1901710679', $phpDoc->getDocumentComponent()->getIdentifier()); unlink($file); $this->assertFalse(file_exists($file)); }
public function testLoadPCIItem($url = '') { $doc = new XmlDocument(); $doc->load(empty($url) === true ? self::samplesDir() . 'custom/interactions/custom_interaction_pci.xml' : $url, true); $item = $doc->getDocumentComponent(); $this->assertInstanceOf('qtism\\data\\AssessmentItem', $item); $this->assertEquals('SimpleExample', $item->getIdentifier()); $this->assertEquals('Example', $item->getTitle()); $this->assertFalse($item->isAdaptive()); $this->assertFalse($item->isTimeDependent()); // responseDeclaration $responseDeclarations = $item->getComponentsByClassName('responseDeclaration'); $this->assertEquals(1, count($responseDeclarations)); $this->assertEquals(BaseType::POINT, $responseDeclarations[0]->getBaseType()); $this->assertEquals(Cardinality::SINGLE, $responseDeclarations[0]->getCardinality()); $this->assertEquals('RESPONSE', $responseDeclarations[0]->getIdentifier()); // templateDeclarations $templateDeclarations = $item->getComponentsByClassName('templateDeclaration'); $this->assertEquals(2, count($templateDeclarations)); $this->assertEquals(BaseType::INTEGER, $templateDeclarations[0]->getBaseType()); $this->assertEquals(Cardinality::SINGLE, $templateDeclarations[0]->getCardinality()); $this->assertEquals('X', $templateDeclarations[0]->getIdentifier()); $this->assertEquals(BaseType::INTEGER, $templateDeclarations[1]->getBaseType()); $this->assertEquals(Cardinality::SINGLE, $templateDeclarations[1]->getCardinality()); $this->assertEquals('Y', $templateDeclarations[1]->getIdentifier()); // customInteraction $customInteractions = $item->getComponentsByClassName('customInteraction'); $this->assertEquals(1, count($customInteractions)); $customInteraction = $customInteractions[0]; $this->assertEquals('RESPONSE', $customInteraction->getResponseIdentifier()); $this->assertEquals('graph1', $customInteraction->getId()); // xml content $customInteractionElt = $customInteraction->getXml()->documentElement; $this->assertEquals('RESPONSE', $customInteractionElt->getAttribute('responseIdentifier')); $this->assertEquals('graph1', $customInteractionElt->getAttribute('id')); $pci = 'http://www.imsglobal.org/xsd/portableCustomInteraction'; // -- pci:portableCustomInteraction $portableCustomInteractionElts = $customInteractionElt->getElementsByTagNameNS($pci, 'portableCustomInteraction'); $this->assertEquals(1, $portableCustomInteractionElts->length); $this->assertEquals('IW30MX6U48JF9120GJS', $portableCustomInteractionElts->item(0)->getAttribute('customInteractionTypeIdentifier')); // --pci:templateVariableMapping $templateVariableMappingElts = $customInteractionElt->getElementsByTagNameNS($pci, 'templateVariableMapping'); $this->assertEquals(2, $templateVariableMappingElts->length); $this->assertEquals('X', $templateVariableMappingElts->item(0)->getAttribute('templateIdentifier')); $this->assertEquals('areaX', $templateVariableMappingElts->item(0)->getAttribute('configurationProperty')); $this->assertEquals('Y', $templateVariableMappingElts->item(1)->getAttribute('templateIdentifier')); $this->assertEquals('areaY', $templateVariableMappingElts->item(1)->getAttribute('configurationProperty')); // --pci:instance $instanceElts = $customInteractionElt->getElementsByTagNameNS($pci, 'instance'); $this->assertEquals(1, $instanceElts->length); // --xhtml:script $xhtml = 'http://www.w3.org/1999/xhtml'; $scriptElts = $instanceElts->item(0)->getElementsByTagNameNS($xhtml, 'script'); $this->assertEquals(2, $scriptElts->length); $this->assertEquals('text/javascript', $scriptElts->item(0)->getAttribute('type')); $this->assertEquals('js/graph.js', $scriptElts->item(0)->getAttribute('src')); $this->assertEquals('text/javascript', $scriptElts->item(1)->getAttribute('type')); $this->assertEquals(7, mb_strpos($scriptElts->item(1)->nodeValue, 'qtiCustomInteractionContext.setConfiguration(', 0, 'UTF-8')); // --xhtml:div $divElts = $instanceElts->item(0)->getElementsByTagNameNS($xhtml, 'div'); $this->assertEquals(1, $divElts->length); $this->assertEquals('graph1_box', $divElts->item(0)->getAttribute('id')); $this->assertEquals('graph', $divElts->item(0)->getAttribute('class')); $this->assertEquals('width:500px; height:500px;', $divElts->item(0)->getAttribute('style')); }
/** * It is sometimes necessary to identify the link between assessmentItemRefs described in a QTI Test definition and the resources * describing items in IMS Manifest file. This utility method helps you to achieve this. * * The method will return an array describing the IMS Manifest resources that were found in an IMS Manifest file on basis of * the assessmentItemRefs found in an AssessmentTest definition. The keys of the arrays are assessmentItemRef identifiers and * values are IMS Manifest Resources. * * If an IMS Manifest Resource cannot be found for a given assessmentItemRef, the value in the returned array will be false. * * @param XmlDocument $test A QTI Test Definition. * @param taoQtiTest_models_classes_ManifestParser $manifestParser A Manifest Parser. * @param string $basePath The base path of the folder the IMS archive is exposed as a file system component. * @return array An array containing two arrays (items and dependencies) where keys are identifiers and values are oat\taoQtiItem\model\qti\Resource objects or false. */ public static function buildAssessmentItemRefsTestMap(XmlDocument $test, taoQtiTest_models_classes_ManifestParser $manifestParser, $basePath) { $assessmentItemRefs = $test->getDocumentComponent()->getComponentsByClassName('assessmentItemRef'); $map = array('items' => array(), 'dependencies' => array()); $itemResources = $manifestParser->getResources(array('imsqti_item_xmlv2p1', 'imsqti_apipitem_xmlv2p1'), taoQtiTest_models_classes_ManifestParser::FILTER_RESOURCE_TYPE); $allResources = $manifestParser->getResources(); // cleanup $basePath. $basePath = rtrim($basePath, "/\\"); $basePath = helpers_File::truePath($basePath); $basePath .= DIRECTORY_SEPARATOR; $documentURI = preg_replace('/^file:\\//', '', $test->getDomDocument()->documentURI); $testPathInfo = pathinfo($documentURI); $testBasePath = tao_helpers_File::truePath($testPathInfo['dirname']) . DIRECTORY_SEPARATOR; foreach ($assessmentItemRefs as $itemRef) { // Find the QTI Resource (in IMS Manifest) related to the item ref. // To achieve this, we compare their path. $itemRefRelativeHref = str_replace('/', DIRECTORY_SEPARATOR, $itemRef->getHref()); $itemRefRelativeHref = ltrim($itemRefRelativeHref, "/\\"); $itemRefCanonicalHref = helpers_File::truePath($testBasePath . $itemRefRelativeHref); $map['items'][$itemRef->getIdentifier()] = false; // Compare with items referenced in the manifest. foreach ($itemResources as $itemResource) { $itemResourceRelativeHref = str_replace('/', DIRECTORY_SEPARATOR, $itemResource->getFile()); $itemResourceRelativeHref = ltrim($itemResourceRelativeHref, "/\\"); $itemResourceCanonicalHref = helpers_File::truePath($basePath . $itemResourceRelativeHref); // With some Windows flavours (Win7, Win8), the $itemRefCanonicalHref comes out with // a leading 'file:\' component. Let's clean this. (str_replace is binary-safe \0/) $os = tao_helpers_Environment::getOperatingSystem(); if ($os === 'WINNT' || $os === 'WIN32' || $os === 'Windows') { $itemRefCanonicalHref = str_replace('file:\\', '', $itemRefCanonicalHref); // And moreover, it sometimes refer the temp directory as Windows\TEMP instead of Windows\Temp. $itemRefCanonicalHref = str_replace('\\TEMP\\', '\\Temp\\', $itemRefCanonicalHref); } // With some MacOS flavours, the $itemRefCanonicalHref comes out with // a leading '/private' component. Clean it! if ($os === 'Darwin') { $itemRefCanonicalHref = str_replace('/private', '', $itemRefCanonicalHref); } if ($itemResourceCanonicalHref == $itemRefCanonicalHref && is_file($itemResourceCanonicalHref)) { // assessmentItemRef <-> IMS Manifest resource successful binding! $map['items'][$itemRef->getIdentifier()] = $itemResource; //get dependencies for each item foreach ($itemResource->getDependencies() as $dependencyIdentifier) { /** @var taoQtiTest_models_classes_QtiResource $resource */ foreach ($allResources as $resource) { if ($dependencyIdentifier == $resource->getIdentifier()) { $map['dependencies'][$dependencyIdentifier] = $resource; break; } } } break; } } } return $map; }
public function testExplodeTestFeedbacks() { $src = self::samplesDir() . 'custom/runtime/testfeedbackrefs_explosion.xml'; $doc = new XmlCompactDocument(); $doc->load($src, true); $doc->setExplodeTestFeedbacks(true); $file = tempnam('/tmp', 'qsm'); $doc->save($file); $pathinfo = pathinfo($file); $path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . 'testFeedback_TF_P01_1.xml'; $this->assertTrue(file_exists($path)); $tfDoc = new XmlDocument(); $tfDoc->load($path); $this->assertEquals('feedback1', $tfDoc->getDocumentComponent()->getIdentifier()); $path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . 'testFeedback_TF_P01_2.xml'; $this->assertTrue(file_exists($path)); $tfDoc = new XmlDocument(); $tfDoc->load($path); $this->assertEquals('feedback2', $tfDoc->getDocumentComponent()->getIdentifier()); $path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . 'testFeedback_TF_testfeedbackrefs_explosion_1.xml'; $this->assertTrue(file_exists($path)); $tfDoc = new XmlDocument(); $tfDoc->load($path); $this->assertEquals('mainfeedback1', $tfDoc->getDocumentComponent()->getIdentifier()); $path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . 'testFeedback_TF_testfeedbackrefs_explosion_2.xml'; $this->assertTrue(file_exists($path)); $tfDoc = new XmlDocument(); $tfDoc->load($path); $this->assertEquals('mainfeedback2', $tfDoc->getDocumentComponent()->getIdentifier()); $this->assertEquals(0, $doc->getDocumentComponent()->containsComponentWithClassName('testFeedback')); }
/** * Embed external resources into the XML * * @param string $originalXml * @throws \tao_models_classes_FileNotFoundException * @return string */ public static function embedAssets($originalXml) { $basedir = dirname($originalXml) . DIRECTORY_SEPARATOR; $xmlDocument = new XmlDocument(); $xmlDocument->load($originalXml, true); //get images and object to base64 their src/data $images = $xmlDocument->getDocumentComponent()->getComponentsByClassName('img'); $objects = $xmlDocument->getDocumentComponent()->getComponentsByClassName('object'); /** @var $image \qtism\data\content\xhtml\Img */ foreach ($images as $image) { $source = $image->getSrc(); $image->setSrc(self::secureEncode($basedir, $source)); } /** @var $object \qtism\data\content\xhtml\Object */ foreach ($objects as $object) { $data = $object->getData(); $object->setData(self::secureEncode($basedir, $data)); } // save the document to a tempfile $newXml = tempnam(sys_get_temp_dir(), 'sharedStimulus_') . '.xml'; $xmlDocument->save($newXml); return $newXml; }
<?php /** * I don't even understand how it is correctly parsed... * But it's safe! */ use qtism\data\storage\xml\XmlDocument; use qtism\runtime\rendering\markup\xhtml\XhtmlRenderingEngine; require_once dirname(__FILE__) . '/../../qtism/qtism.php'; $doc = new XmlDocument(); $doc->load('../samples/rendering/php_highjacking_3.xml', true); $renderer = new XhtmlRenderingEngine(); $rendering = $renderer->render($doc->getDocumentComponent()); $rendering->formatOutput = true; echo $rendering->saveXML();
public function testAmps() { $file = self::samplesDir() . 'custom/amps.xml'; $doc = new XmlDocument(); $doc->load($file); $root = $doc->getDocumentComponent(); $divs = $root->getComponentsByClassName('div'); $this->assertEquals(1, count($divs)); $divContent = $divs[0]->getContent(); $divText = $divContent[0]; $this->assertEquals('Hello there & there! I am trying to make <you> "crazy"', $divText->getcontent()); }