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]; }
/** * @dataProvider qtiImplementationGuideAssessmentTestFiles * * @param string $uri The URI describing the file to load. */ public function testLoadFromStringNoSchemaValidate($uri) { $doc = new XmlDocument('2.1'); $doc->loadFromString(file_get_contents($uri)); $this->assertInstanceOf('qtism\\data\\storage\\xml\\XmlDocument', $doc); $this->assertInstanceOf('qtism\\data\\AssessmentTest', $doc->getDocumentComponent()); }
public function testMappingMcqQuestion() { $questionJson = $this->getFixtureFileContents('learnosityjsons/item_mcq.json'); $question = json_decode($questionJson, true); list($xmlString, $messages) = Converter::convertLearnosityToQtiItem($question); $this->assertNotNull($xmlString); $this->assertTrue(StringUtil::startsWith($xmlString, '<?xml version="1.0" encoding="UTF-8"?> <assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"')); $document = new XmlDocument(); $document->loadFromString($xmlString); $this->assertNotNull($document); }
/** * 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 { $qtiXmlFileContent = QtiFile::getQtiFileContent($item); $qtiXmlDoc = new XmlDocument(); $qtiXmlDoc->loadFromString($qtiXmlFileContent); } catch (StorageException $e) { $msg = "An error occurred while loading QTI-XML file at expected location '{$qtiXmlFilePath}'."; common_Logger::e($e->getPrevious() !== null ? $e->getPrevious()->getMessage() : $e->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); } }
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 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); }
public static function convertLearnosityToQtiItem(array $data) { $jsonType = self::guessLearnosityJsonDataType($data); // Handle `item` which contains both a single item and one or more questions/features if ($jsonType === self::LEARNOSITY_DATA_ITEM) { list($xmlString, $messages) = self::convertLearnosityItem($data); // Handle if just question } else { if ($jsonType === self::LEARNOSITY_DATA_QUESTION) { list($xmlString, $messages) = self::convertLearnosityQuestion($data); // Handle if just question data } else { if ($jsonType === self::LEARNOSITY_DATA_QUESTION_DATA) { list($xmlString, $messages) = self::convertLearnosityQuestionData($data); } else { throw new \Exception('Unknown JSON format'); } } } // Validate them before proceeding by feeding it back try { $document = new XmlDocument(); $document->loadFromString($xmlString); } catch (\Exception $e) { LogService::log('Unknown error occurred. The QTI XML produced may not be valid'); } return [$xmlString, $messages]; }
/** * 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'); } }
/** * 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); } }
public function testUnknownClassWhileLoadingBecauseOfVersion3() { $expectedMsg = "'bdo' components are not supported in QTI version '2.0.0'"; $this->setExpectedException('qtism\\data\\storage\\xml\\XmlStorageException', $expectedMsg, XmlStorageException::VERSION); $doc = new XmlDocument('2.0.0'); $doc->loadFromString(' <div> <bdo dir="rtl">I am reversed!</bdo> </div>'); }
/** * Get the QTI reprensentation of a test content. * * @param core_kernel_classes_Resource $test the test to get the content from * @param type $validate enable validation * @return XmlDocument the QTI representation from the test content * @throws taoQtiTest_models_classes_QtiTestServiceException */ public function getDoc(core_kernel_classes_Resource $test) { $doc = new XmlDocument('2.1'); $doc->loadFromString($this->getQtiTestFile($test)->read()); return $doc; }