/** * Import a QTI Test and its dependent Items into the TAO Platform. * * @param core_kernel_classes_Class $targetClass The RDFS Class where Ontology resources must be created. * @param oat\taoQtiItem\model\qti\Resource $qtiTestResource The QTI Test Resource representing the IMS QTI Test to be imported. * @param taoQtiTest_models_classes_ManifestParser $manifestParser The parser used to retrieve the IMS Manifest. * @param string $folder The absolute path to the folder where the IMS archive containing the test content * @return common_report_Report A report about how the importation behaved. */ protected function importTest(core_kernel_classes_Class $targetClass, Resource $qtiTestResource, taoQtiTest_models_classes_ManifestParser $manifestParser, $folder) { $itemImportService = ImportService::singleton(); $itemService = taoItems_models_classes_ItemsService::singleton(); $testClass = $targetClass; // Create an RDFS resource in the knowledge base that will hold // the information about the imported QTI Test. $testResource = $this->createInstance($testClass); $qtiTestModelResource = new core_kernel_classes_Resource(INSTANCE_TEST_MODEL_QTI); $modelProperty = new core_kernel_classes_Property(PROPERTY_TEST_TESTMODEL); $testResource->editPropertyValues($modelProperty, $qtiTestModelResource); // Create the report that will hold information about the import // of $qtiTestResource in TAO. $report = new common_report_Report(common_report_Report::TYPE_INFO); // The class where the items that belong to the test will be imported. $itemClass = new core_kernel_classes_Class(TAO_ITEM_CLASS); $targetClass = $itemClass->createSubClass($testResource->getLabel()); // Load and validate the manifest $qtiManifestParser = new taoQtiTest_models_classes_ManifestParser($folder . 'imsmanifest.xml'); $qtiManifestParser->validate(); // Prepare Metadata mechanisms. $metadataMapping = oat\taoQtiItem\model\qti\Service::singleton()->getMetadataRegistry()->getMapping(); $metadataInjectors = array(); $metadataGuardians = array(); $metadataClassLookups = array(); $metadataValues = array(); $domManifest = new DOMDocument('1.0', 'UTF-8'); $domManifest->load($folder . 'imsmanifest.xml'); foreach ($metadataMapping['injectors'] as $injector) { $metadataInjectors[] = new $injector(); } foreach ($metadataMapping['guardians'] as $guardian) { $metadataGuardians[] = new $guardian(); } foreach ($metadataMapping['classLookups'] as $classLookup) { $metadataClassLookups[] = new $classLookup(); } foreach ($metadataMapping['extractors'] as $extractor) { $metadataExtractor = new $extractor(); $metadataValues = array_merge($metadataValues, $metadataExtractor->extract($domManifest)); } // Set up $report with useful information for client code (especially for rollback). $reportCtx = new stdClass(); $reportCtx->manifestResource = $qtiTestResource; $reportCtx->rdfsResource = $testResource; $reportCtx->itemClass = $targetClass; $reportCtx->items = array(); $report->setData($reportCtx); // Expected test.xml file location. $expectedTestFile = $folder . str_replace('/', DIRECTORY_SEPARATOR, $qtiTestResource->getFile()); // Already imported test items (qti xml file paths). $alreadyImportedTestItemFiles = array(); // -- Check if the file referenced by the test QTI resource exists. if (is_readable($expectedTestFile) === false) { $report->add(common_report_Report::createFailure(__('No file found at location "%s".', $qtiTestResource->getFile()))); } else { // -- Load the test in a QTISM flavour. $testDefinition = new XmlDocument(); try { $testDefinition->load($expectedTestFile, true); // -- Load all items related to test. $itemError = false; // discover test's base path. $dependencies = taoQtiTest_helpers_Utils::buildAssessmentItemRefsTestMap($testDefinition, $manifestParser, $folder); if (count($dependencies['items']) > 0) { foreach ($dependencies['items'] as $assessmentItemRefId => $qtiDependency) { if ($qtiDependency !== false) { if (Resource::isAssessmentItem($qtiDependency->getType())) { $resourceIdentifier = $qtiDependency->getIdentifier(); // Check if the item is already stored in the bank. foreach ($metadataGuardians as $guardian) { if (isset($metadataValues[$resourceIdentifier]) === true) { if (($guard = $guardian->guard($metadataValues[$resourceIdentifier])) !== false) { common_Logger::i("Item with identifier '{$resourceIdentifier}' already in Item Bank."); $msg = __('The IMS QTI Item referenced as "%s" in the IMS Manifest file was already stored in the Item Bank.', $resourceIdentifier); $report->add(common_report_Report::createInfo($msg, $guard)); $reportCtx->items[$assessmentItemRefId] = $guard; // Simply do not import again. continue 2; } } } // Determine target class from metadata, if possible. // This is applied to items, not for test definitions. // The test definitions' target class will not be affected // by class lookups. $lookupTargetClass = false; foreach ($metadataClassLookups as $classLookup) { if (isset($metadataValues[$resourceIdentifier]) === true) { if (($lookupTargetClass = $classLookup->lookup($metadataValues[$resourceIdentifier])) !== false) { break; } } } $qtiFile = $folder . str_replace('/', DIRECTORY_SEPARATOR, $qtiDependency->getFile()); // Skip if $qtiFile already imported (multiple assessmentItemRef "hrefing" the same file). if (array_key_exists($qtiFile, $alreadyImportedTestItemFiles) === false) { $isApip = $qtiDependency->getType() === 'imsqti_apipitem_xmlv2p1'; $itemReport = $itemImportService->importQtiItem($folder, $qtiDependency, $lookupTargetClass !== false ? $lookupTargetClass : $targetClass, $isApip, $dependencies['dependencies']); $rdfItem = $itemReport->getData(); if ($rdfItem) { $reportCtx->items[$assessmentItemRefId] = $rdfItem; $alreadyImportedTestItemFiles[$qtiFile] = $rdfItem; $itemReport->setMessage(__('IMS QTI Item referenced as "%s" in the IMS Manifest file successfully imported.', $qtiDependency->getIdentifier())); } else { $itemReport->setType(common_report_Report::TYPE_ERROR); $itemReport->setMessage(__('IMS QTI Item referenced as "%s" in the IMS Manifest file could not be imported.', $qtiDependency->getIdentifier())); $itemError = $itemError === false ? true : $itemError; } $report->add($itemReport); } else { $reportCtx->items[$assessmentItemRefId] = $alreadyImportedTestItemFiles[$qtiFile]; } } } else { $msg = __('The dependency to the IMS QTI AssessmentItemRef "%s" in the IMS Manifest file could not be resolved.', $assessmentItemRefId); $report->add(common_report_Report::createFailure($msg)); $itemError = $itemError === false ? true : $itemError; } } // If items did not produce errors, we import the test definition. if ($itemError === false) { common_Logger::i('Importing test...'); // Second step is to take care of the test definition and the related media (auxiliary files). // 1. Import test definition (i.e. the QTI-XML Test file). $testContent = $this->importTestDefinition($testResource, $testDefinition, $qtiTestResource, $reportCtx->items, $folder, $report); if ($testContent !== false) { // 2. Import test auxilliary files (e.g. stylesheets, images, ...). $this->importTestAuxiliaryFiles($testContent, $qtiTestResource, $folder, $report); // 3. Give meaningful names to resources. $testTitle = $testDefinition->getDocumentComponent()->getTitle(); $testResource->setLabel($testDefinition->getDocumentComponent()->getTitle()); $targetClass->setLabel($testDefinition->getDocumentComponent()->getTitle()); // 4. if $targetClass does not contain any instances (because everything resolved by class lookups), // Just delete it. if ($targetClass->countInstances() == 0) { $targetClass->delete(); } } } else { $msg = __("One or more dependent IMS QTI Items could not be imported."); $report->add(common_report_Report::createFailure($msg)); } } else { // No depencies found (i.e. no item resources bound to the test). $msg = __("No reference to any IMS QTI Item found."); $report->add(common_report_Report::createFailure($msg)); } } catch (StorageException $e) { // Source of the exception = $testDefinition->load() // What is the reason ? $finalErrorString = ''; $eStrs = array(); if (($libXmlErrors = $e->getErrors()) !== null) { foreach ($libXmlErrors as $libXmlError) { $eStrs[] = __('XML error at line %1$d column %2$d "%3$s".', $libXmlError->line, $libXmlError->column, trim($libXmlError->message)); } } $finalErrorString = implode("\n", $eStrs); if (empty($finalErrorString) === true) { // Not XML malformation related. No info from LibXmlErrors extracted. if (($previous = $e->getPrevious()) != null) { // Useful information could be found here. $finalErrorString = $previous->getMessage(); if ($previous instanceof UnmarshallingException) { $domElement = $previous->getDOMElement(); $finalErrorString = __('Inconsistency at line %1d:', $domElement->getLineNo()) . ' ' . $previous->getMessage(); } } else { $finalErrorString = __("Unknown error."); } } $msg = __("Error found in the IMS QTI Test:\n%s", $finalErrorString); $report->add(common_report_Report::createFailure($msg)); } } if ($report->containsError() === false) { $report->setType(common_report_Report::TYPE_SUCCESS); $msg = __("IMS QTI Test referenced as \"%s\" in the IMS Manifest file successfully imported.", $qtiTestResource->getIdentifier()); $report->setMessage($msg); } else { $report->setType(common_report_Report::TYPE_ERROR); $msg = __("The IMS QTI Test referenced as \"%s\" in the IMS Manifest file could not be imported.", $qtiTestResource->getIdentifier()); $report->setMessage($msg); } return $report; }
/** * Import a QTI Test and its dependent Items into the TAO Platform. * * @param core_kernel_classes_Class $targetClass The RDFS Class where Ontology resources must be created. * @param oat\taoQtiItem\model\qti\Resource $qtiTestResource The QTI Test Resource representing the IMS QTI Test to be imported. * @param taoQtiTest_models_classes_ManifestParser $manifestParser The parser used to retrieve the IMS Manifest. * @param string $folder The absolute path to the folder where the IMS archive containing the test content * @return common_report_Report A report about how the importation behaved. */ protected function importTest(core_kernel_classes_Class $targetClass, Resource $qtiTestResource, taoQtiTest_models_classes_ManifestParser $manifestParser, $folder) { $itemImportService = ImportService::singleton(); $itemService = taoItems_models_classes_ItemsService::singleton(); $testClass = $targetClass; // Create an RDFS resource in the knowledge base that will hold // the information about the imported QTI Test. $testResource = $this->createInstance($testClass); $qtiTestModelResource = new core_kernel_classes_Resource(INSTANCE_TEST_MODEL_QTI); $modelProperty = new core_kernel_classes_Property(PROPERTY_TEST_TESTMODEL); $testResource->editPropertyValues($modelProperty, $qtiTestModelResource); // Create the report that will hold information about the import // of $qtiTestResource in TAO. $report = new common_report_Report(common_report_Report::TYPE_INFO); // The class where the items that belong to the test will be imported. $itemClass = new core_kernel_classes_Class(TAO_ITEM_CLASS); $targetClass = $itemClass->createSubClass($testResource->getLabel()); // Load and validate the manifest $qtiManifestParser = new taoQtiTest_models_classes_ManifestParser($folder . 'imsmanifest.xml'); $qtiManifestParser->validate(); // Set up $report with useful information for client code (especially for rollback). $reportCtx = new stdClass(); $reportCtx->manifestResource = $qtiTestResource; $reportCtx->rdfsResource = $testResource; $reportCtx->itemClass = $targetClass; $reportCtx->items = array(); $report->setData($reportCtx); // Expected test.xml file location. $expectedTestFile = $folder . str_replace('/', DIRECTORY_SEPARATOR, $qtiTestResource->getFile()); // Already imported test items (qti xml file paths). $alreadyImportedTestItemFiles = array(); // -- Check if the file referenced by the test QTI resource exists. if (is_readable($expectedTestFile) === false) { $report->add(common_report_Report::createFailure(__('No file found at location "%s".', $qtiTestResource->getFile()))); } else { // -- Load the test in a QTISM flavour. $testDefinition = new XmlDocument(); try { $testDefinition->load($expectedTestFile, true); // -- Load all items related to test. $itemError = false; // discover test's base path. $dependencies = taoQtiTest_helpers_Utils::buildAssessmentItemRefsTestMap($testDefinition, $manifestParser, $folder); if (count($dependencies) > 0) { foreach ($dependencies as $assessmentItemRefId => $qtiDependency) { if ($qtiDependency !== false) { if (Resource::isAssessmentItem($qtiDependency->getType())) { $qtiFile = $folder . str_replace('/', DIRECTORY_SEPARATOR, $qtiDependency->getFile()); // Skip if $qtiFile already imported (multiple assessmentItemRef "hrefing" the same file). if (array_key_exists($qtiFile, $alreadyImportedTestItemFiles) === false) { $itemReport = $itemImportService->importQTIFile($qtiFile, $targetClass); $rdfItem = $itemReport->getData(); if ($rdfItem) { $itemPath = taoItems_models_classes_ItemsService::singleton()->getItemFolder($rdfItem); foreach ($qtiDependency->getAuxiliaryFiles() as $auxResource) { // $auxResource is a relativ URL, so we need to replace the slashes with directory separators $auxPath = $folder . str_replace('/', DIRECTORY_SEPARATOR, $auxResource); // does the file referenced by $auxPath exist? if (is_readable($auxPath) === true) { $relPath = helpers_File::getRelPath($qtiFile, $auxPath); $destPath = $itemPath . $relPath; tao_helpers_File::copy($auxPath, $destPath, true); } else { $msg = __('Auxiliary file not found at location "%s".', $auxResource); $itemReport->add(new common_report_Report(common_report_Report::TYPE_WARNING, $msg)); } } $reportCtx->items[$assessmentItemRefId] = $rdfItem; $alreadyImportedTestItemFiles[$qtiFile] = $rdfItem; $itemReport->setMessage(__('IMS QTI Item referenced as "%s" in the IMS Manifest file successfully imported.', $qtiDependency->getIdentifier())); } else { $itemReport->setType(common_report_Report::TYPE_ERROR); $itemReport->setMessage(__('IMS QTI Item referenced as "%s" in the IMS Manifest file could not be imported.', $qtiDependency->getIdentifier())); $itemError = $itemError === false ? true : $itemError; } $report->add($itemReport); } else { $reportCtx->items[$assessmentItemRefId] = $alreadyImportedTestItemFiles[$qtiFile]; } } } else { $msg = __('The dependency to the IMS QTI AssessmentItemRef "%s" in the IMS Manifest file could not be resolved.', $assessmentItemRefId); $report->add(common_report_Report::createFailure($msg)); $itemError = $itemError === false ? true : $itemError; } } // If items did not produce errors, we import the test definition. if ($itemError === false) { common_Logger::i('Importing test...'); // Second step is to take care of the test definition and the related media (auxiliary files). // 1. Import test definition (i.e. the QTI-XML Test file). $testContent = $this->importTestDefinition($testResource, $testDefinition, $qtiTestResource, $reportCtx->items, $folder, $report); if ($testContent !== false) { // 2. Import test auxilliary files (e.g. stylesheets, images, ...). $this->importTestAuxiliaryFiles($testContent, $qtiTestResource, $folder, $report); // 3. Give meaningful names to resources. $testTitle = $testDefinition->getDocumentComponent()->getTitle(); $testResource->setLabel($testDefinition->getDocumentComponent()->getTitle()); $targetClass->setLabel($testDefinition->getDocumentComponent()->getTitle()); } } else { $msg = __("One or more dependent IMS QTI Items could not be imported."); $report->add(common_report_Report::createFailure($msg)); } } else { // No depencies found (i.e. no item resources bound to the test). $msg = __("No reference to any IMS QTI Item found."); $report->add(common_report_Report::createFailure($msg)); } } catch (StorageException $e) { // Source of the exception = $testDefinition->load() // What is the reason ? $finalErrorString = ''; $eStrs = array(); if (($libXmlErrors = $e->getErrors()) !== null) { foreach ($libXmlErrors as $libXmlError) { $eStrs[] = __('XML error at line %1$d column %2$d "%3$s".', $libXmlError->line, $libXmlError->column, trim($libXmlError->message)); } } $finalErrorString = implode("\n", $eStrs); if (empty($finalErrorString) === true) { // Not XML malformation related. No info from LibXmlErrors extracted. if (($previous = $e->getPrevious()) != null) { // Useful information could be found here. $finalErrorString = $previous->getMessage(); if ($previous instanceof UnmarshallingException) { $domElement = $previous->getDOMElement(); $finalErrorString = __('Inconsistency at line %1d:', $domElement->getLineNo()) . ' ' . $previous->getMessage(); } } else { $finalErrorString = __("Unknown error."); } } $msg = __("Error found in the IMS QTI Test:\n%s", $finalErrorString); $report->add(common_report_Report::createFailure($msg)); } } if ($report->containsError() === false) { $report->setType(common_report_Report::TYPE_SUCCESS); $msg = __("IMS QTI Test referenced as \"%s\" in the IMS Manifest file successfully imported.", $qtiTestResource->getIdentifier()); $report->setMessage($msg); } else { $report->setType(common_report_Report::TYPE_ERROR); $msg = __("The IMS QTI Test referenced as \"%s\" in the IMS Manifest file could not be imported.", $qtiTestResource->getIdentifier()); $report->setMessage($msg); } return $report; }