/**
  * Store a file referenced by $qtiResource into the final $testContent folder. If the path provided
  * by $qtiResource contains sub-directories, they will be created before copying the file (even
  * if $copy = false).
  * 
  * @param Directory $testContent The pointer to the TAO Test Content folder.
  * @param oat\taoQtiItem\model\qti\Resource|string $qtiResource The QTI resource to be copied into $testContent. If given as a string, it must be the relative (to the IMS QTI Package) path to the resource file.
  * @param string $origin The path to the directory (root folder of extracted IMS QTI package) containing the QTI resource to be copied.
  * @param boolean $copy If set to false, the file will not be actually copied.
  * @param string $rename A new filename  e.g. 'file.css' to be used at storage time.
  * @return string The path were the file was copied/has to be copied (depending on the $copy argument).
  * @throws InvalidArgumentException If one of the above arguments is invalid.
  * @throws common_Exception If the copy fails.
  */
 public static function storeQtiResource(Directory $testContent, $qtiResource, $origin, $copy = true, $rename = '')
 {
     $fss = ServiceManager::getServiceManager()->get(FileSystemService::SERVICE_ID);
     $fs = $fss->getFileSystem($testContent->getFileSystem()->getId());
     $contentPath = $testContent->getPrefix();
     $ds = DIRECTORY_SEPARATOR;
     $contentPath = rtrim($contentPath, $ds);
     if ($qtiResource instanceof Resource) {
         $filePath = $qtiResource->getFile();
     } else {
         if (is_string($qtiResource) === true) {
             $filePath = $qtiResource;
         } else {
             throw new InvalidArgumentException("The 'qtiResource' argument must be a string or a taoQTI_models_classes_QTI_Resource object.");
         }
     }
     $resourcePathinfo = pathinfo($filePath);
     if (empty($resourcePathinfo['dirname']) === false && $resourcePathinfo['dirname'] !== '.') {
         // The resource file is not at the root of the archive but in a sub-folder.
         // Let's copy it in the same way into the Test Content folder.
         $breadCrumb = $contentPath . $ds . str_replace('/', $ds, $resourcePathinfo['dirname']);
         $breadCrumb = rtrim($breadCrumb, $ds);
         $finalName = empty($rename) === true ? $resourcePathinfo['filename'] . '.' . $resourcePathinfo['extension'] : $rename;
         $finalPath = $breadCrumb . $ds . $finalName;
     } else {
         // The resource file is at the root of the archive.
         // Overwrite template test.xml (created by self::createContent() method above) file with the new one.
         $finalName = empty($rename) === true ? $resourcePathinfo['filename'] . '.' . $resourcePathinfo['extension'] : $rename;
         $finalPath = $contentPath . $ds . $finalName;
     }
     if ($copy === true) {
         $origin = str_replace('/', $ds, $origin);
         $origin = rtrim($origin, $ds);
         $sourcePath = $origin . $ds . str_replace('/', $ds, $filePath);
         if (is_readable($sourcePath) === false) {
             throw new common_Exception("An error occured while copying the QTI resource from '{$sourcePath}' to '{$finalPath}'.");
         }
         $fh = fopen($sourcePath, 'r');
         $success = $fs->writeStream($finalPath, $fh);
         fclose($fh);
         if (!$success) {
             throw new common_Exception("An error occured while copying the QTI resource from '{$sourcePath}' to '{$finalPath}'.");
         }
     }
     return $finalPath;
 }
 /**
  * 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;
 }
 /**
  * @param $folder
  * @param \taoQtiTest_models_classes_QtiResource $qtiItemResource
  * @param $itemClass
  * @param bool|false $extractApip
  * @param array $dependencies
  * @return common_report_Report
  * @throws common_exception_Error
  */
 public function importQtiItem($folder, Resource $qtiItemResource, $itemClass, $extractApip = false, $dependencies = array())
 {
     try {
         //load the information about resources in the manifest
         $itemService = taoItems_models_classes_ItemsService::singleton();
         $qtiService = Service::singleton();
         // The metadata import feature needs a DOM representation of the manifest.
         $domManifest = new DOMDocument('1.0', 'UTF-8');
         $domManifest->load($folder . 'imsmanifest.xml');
         $metadataMapping = $qtiService->getMetadataRegistry()->getMapping();
         $metadataInjectors = array();
         $metadataGuardians = array();
         $metadataClassLookups = array();
         $metadataValues = array();
         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));
         }
         $sources = MediaService::singleton()->getWritableSources();
         $sharedStorage = array_shift($sources);
         $sharedFiles = array();
         try {
             $resourceIdentifier = $qtiItemResource->getIdentifier();
             // Use the guardians to check whether or not the item has to be imported.
             foreach ($metadataGuardians as $guardian) {
                 if (isset($metadataValues[$resourceIdentifier]) === true) {
                     if (($guard = $guardian->guard($metadataValues[$resourceIdentifier])) !== false) {
                         $msg = __('The IMS QTI Item referenced as "%s" in the IMS Manifest file was already stored in the Item Bank.', $qtiItemResource->getIdentifier());
                         $report = common_report_Report::createInfo($msg, $guard);
                         // Simply do not import again.
                         return $report;
                     }
                 }
             }
             $targetClass = false;
             // Use the classLookups to determine where the item has to go.
             foreach ($metadataClassLookups as $classLookup) {
                 if (isset($metadataValues[$resourceIdentifier]) === true) {
                     if (($targetClass = $classLookup->lookup($metadataValues[$resourceIdentifier])) !== false) {
                         break;
                     }
                 }
             }
             $qtiFile = $folder . $qtiItemResource->getFile();
             $qtiModel = $this->createQtiItemModel($qtiFile);
             $rdfItem = $this->createRdfItem($targetClass !== false ? $targetClass : $itemClass, $qtiModel);
             $name = $rdfItem->getLabel();
             $itemContent = $itemService->getItemContent($rdfItem);
             $xincluded = array();
             foreach ($qtiModel->getBody()->getComposingElements('oat\\taoQtiItem\\model\\qti\\Xinclude') as $xincludeEle) {
                 $xincluded[] = $xincludeEle->attr('href');
             }
             $local = new LocalItemSource(array('item' => $rdfItem));
             foreach ($qtiItemResource->getAuxiliaryFiles() as $auxResource) {
                 // file on FS
                 $auxFile = $folder . str_replace('/', DIRECTORY_SEPARATOR, $auxResource);
                 // rel path in item
                 $auxPath = str_replace(DIRECTORY_SEPARATOR, '/', helpers_File::getRelPath($qtiFile, $auxFile));
                 if (!empty($sharedStorage) && in_array($auxPath, $xincluded)) {
                     $md5 = md5_file($auxFile);
                     if (isset($sharedFiles[$md5])) {
                         $info = $sharedFiles[$md5];
                         \common_Logger::i('Auxiliary file \'' . $auxPath . '\' linked to shared storage.');
                     } else {
                         // TODO cleanup sharedstimulus import/export
                         // move to taoQti item or library
                         // validate the shared stimulus
                         SharedStimulusImporter::isValidSharedStimulus($auxFile);
                         // embed assets in the shared stimulus
                         $newXmlFile = SharedStimulusPackageImporter::embedAssets($auxFile);
                         $info = $sharedStorage->add($newXmlFile, basename($auxFile), $name);
                         if (method_exists($sharedStorage, 'forceMimeType')) {
                             // add() does not return link, so we need to parse it
                             $resolver = new ItemMediaResolver($rdfItem, '');
                             $asset = $resolver->resolve($info['uri']);
                             $sharedStorage->forceMimeType($asset->getMediaIdentifier(), 'application/qti+xml');
                         }
                         $sharedFiles[$md5] = $info;
                         \common_Logger::i('Auxiliary file \'' . $auxPath . '\' added to shared storage.');
                     }
                 } else {
                     // store locally, in a safe directory
                     $safePath = '';
                     if (dirname($auxPath) !== '.') {
                         $safePath = str_replace('../', '', dirname($auxPath)) . '/';
                     }
                     $info = $local->add($auxFile, basename($auxFile), $safePath);
                     \common_Logger::i('Auxiliary file \'' . $auxPath . '\' copied.');
                 }
                 // replace uri if changed
                 if ($auxPath != ltrim($info['uri'], '/')) {
                     $itemContent = str_replace($auxPath, $info['uri'], $itemContent);
                 }
             }
             foreach ($qtiItemResource->getDependencies() as $dependency) {
                 // file on FS
                 if (isset($dependencies[$dependency])) {
                     $auxFile = $dependencies[$dependency]->getFile();
                     $auxFile = $folder . str_replace('/', DIRECTORY_SEPARATOR, $auxFile);
                     // rel path in item
                     $auxPath = str_replace(DIRECTORY_SEPARATOR, '/', helpers_File::getRelPath($qtiFile, $auxFile));
                     if (!empty($sharedStorage) && in_array($auxPath, $xincluded)) {
                         $md5 = md5_file($auxFile);
                         if (isset($sharedFiles[$md5])) {
                             $info = $sharedFiles[$md5];
                             \common_Logger::i('Auxiliary file \'' . $auxPath . '\' linked to shared storage.');
                         } else {
                             // TODO cleanup sharedstimulus import/export
                             // move to taoQti item or library
                             // validate the shared stimulus
                             SharedStimulusImporter::isValidSharedStimulus($auxFile);
                             // embed assets in the shared stimulus
                             $newXmlFile = SharedStimulusPackageImporter::embedAssets($auxFile);
                             $info = $sharedStorage->add($newXmlFile, basename($auxFile), $name);
                             if (method_exists($sharedStorage, 'forceMimeType')) {
                                 // add() does not return link, so we need to parse it
                                 $resolver = new ItemMediaResolver($rdfItem, '');
                                 $asset = $resolver->resolve($info['uri']);
                                 $sharedStorage->forceMimeType($asset->getMediaIdentifier(), 'application/qti+xml');
                             }
                             $sharedFiles[$md5] = $info;
                             \common_Logger::i('Auxiliary file \'' . $auxPath . '\' added to shared storage.');
                         }
                     } else {
                         // store locally, in a safe directory
                         $safePath = '';
                         if (dirname($auxPath) !== '.') {
                             $safePath = str_replace('../', '', dirname($auxPath)) . '/';
                         }
                         $info = $local->add($auxFile, basename($auxFile), $safePath);
                         \common_Logger::i('Auxiliary file \'' . $auxPath . '\' copied.');
                     }
                     // replace uri if changed
                     if ($auxPath != ltrim($info['uri'], '/')) {
                         $itemContent = str_replace($auxPath, $info['uri'], $itemContent);
                     }
                 }
             }
             // Finally, import metadata.
             $this->importItemMetadata($metadataValues, $qtiItemResource, $rdfItem, $metadataInjectors);
             // And Apip if wanted
             if ($extractApip) {
                 $this->storeApip($qtiFile, $rdfItem);
             }
             $itemService->setItemContent($rdfItem, $itemContent);
             $msg = __('The IMS QTI Item referenced as "%s" in the IMS Manifest file was successfully imported.', $qtiItemResource->getIdentifier());
             $report = common_report_Report::createSuccess($msg, $rdfItem);
         } catch (ParsingException $e) {
             $report = new common_report_Report(common_report_Report::TYPE_ERROR, $e->getUserMessage());
         } catch (ValidationException $ve) {
             $report = \common_report_Report::createFailure(__('IMS QTI Item referenced as "%s" in the IMS Manifest file could not be imported.', $qtiItemResource->getIdentifier()));
             $report->add($ve->getReport());
         } catch (Exception $e) {
             // an error occured during a specific item
             $report = new common_report_Report(common_report_Report::TYPE_ERROR, __("An unknown error occured while importing the IMS QTI Package."));
             common_Logger::e($e->getMessage());
         }
     } catch (ValidationException $ve) {
         $validationReport = \common_report_Report::createFailure("The IMS Manifest file could not be validated");
         $validationReport->add($ve->getReport());
         $report->setMessage(__("No Items could be imported from the given IMS QTI package."));
         $report->setType(common_report_Report::TYPE_ERROR);
         $report->add($validationReport);
     } catch (common_exception_UserReadableException $e) {
         $report = new common_report_Report(common_report_Report::TYPE_ERROR, __($e->getUserMessage()));
         $report->add($e);
     }
     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;
 }
 /**
  * Import dependencies files
  *
  * @param QtiResource $qtiItemResource
  * @param $dependencies
  * @return $this
  * @throws AssetManagerException
  */
 public function importDependencyFiles(QtiResource $qtiItemResource, $dependencies)
 {
     $qtiFile = $this->getSource() . \helpers_File::urlToPath($qtiItemResource->getFile());
     foreach ($qtiItemResource->getDependencies() as $dependenciesFile) {
         if (!isset($dependencies[$dependenciesFile])) {
             continue;
         }
         $absolutePath = $this->getAbsolutePath($dependencies[$dependenciesFile]->getFile());
         $relativePath = $this->getRelativePath($qtiFile, $absolutePath);
         try {
             $this->importAsset($absolutePath, $relativePath);
         } catch (\common_Exception $e) {
             throw new AssetManagerException('Error occurs during dependency assets handling for item: ' . $qtiItemResource->getIdentifier() . ', assetFile: ' . $relativePath, 0, $e);
         }
     }
     return $this;
 }
 /**
  * @param $folder
  * @param \oat\taoQtiItem\model\qti\Resource $qtiItemResource
  * @param $itemClass
  * @param array $dependencies
  * @param array $metadataValues
  * @param array $metadataInjectors
  * @param array $metadataGuardians
  * @param array $metadataClassLookups
  * @param array $sharedFiles
  * @param array $createdClass
  * @return common_report_Report
  * @throws common_exception_Error
  */
 public function importQtiItem($folder, Resource $qtiItemResource, $itemClass, array $dependencies = array(), array $metadataValues = array(), array $metadataInjectors = array(), array $metadataGuardians = array(), array $metadataClassLookups = array(), array $sharedFiles = array(), &$createdClasses = array())
 {
     try {
         $qtiService = Service::singleton();
         //load the information about resources in the manifest
         try {
             $resourceIdentifier = $qtiItemResource->getIdentifier();
             // Use the guardians to check whether or not the item has to be imported.
             foreach ($metadataGuardians as $guardian) {
                 if (isset($metadataValues[$resourceIdentifier]) === true) {
                     if (($guard = $guardian->guard($metadataValues[$resourceIdentifier])) !== false) {
                         \common_Logger::i("Resource '{$resourceIdentifier}' is already stored in the database and will not be imported.");
                         $msg = __('The IMS QTI Item referenced as "%s" in the IMS Manifest file was already stored in the Item Bank.', $resourceIdentifier);
                         $report = common_report_Report::createInfo($msg, $guard);
                         // Simply do not import again.
                         return $report;
                     }
                 }
             }
             $targetClass = false;
             // Use the classLookups to determine where the item has to go.
             foreach ($metadataClassLookups as $classLookup) {
                 if (isset($metadataValues[$resourceIdentifier]) === true) {
                     \common_Logger::i("Target Class Lookup for resource '{$resourceIdentifier}' ...");
                     if (($targetClass = $classLookup->lookup($metadataValues[$resourceIdentifier])) !== false) {
                         \common_Logger::i("Class Lookup Successful. Resource '{$resourceIdentifier}' will be stored in RDFS Class '" . $targetClass->getUri() . "'.");
                         if ($classLookup instanceof MetadataClassLookupClassCreator) {
                             $createdClasses = $classLookup->createdClasses();
                         }
                         break;
                     }
                 }
             }
             $qtiFile = $folder . helpers_File::urlToPath($qtiItemResource->getFile());
             common_Logger::i('file :: ' . $qtiItemResource->getFile());
             $qtiModel = $this->createQtiItemModel($qtiFile);
             $rdfItem = $this->createRdfItem($targetClass !== false ? $targetClass : $itemClass, $qtiModel);
             $itemAssetManager = new AssetManager();
             $itemAssetManager->setItemContent($qtiModel->toXML());
             $itemAssetManager->setSource($folder);
             /**
              * Load asset handler following priority handler defined by you
              * The first applicable will be used to import assets
              */
             /** Portable element handler */
             $peHandler = new PortableAssetHandler($qtiModel, dirname($qtiFile));
             $itemAssetManager->loadAssetHandler($peHandler);
             /** Shared stimulus handler */
             $sharedStimulusHandler = new SharedStimulusAssetHandler();
             $sharedStimulusHandler->setQtiModel($qtiModel)->setItemSource(new ItemMediaResolver($rdfItem, ''))->setSharedFiles($sharedFiles)->setParentPath($rdfItem->getLabel());
             $itemAssetManager->loadAssetHandler($sharedStimulusHandler);
             /** Local storage handler */
             $localHandler = new LocalAssetHandler();
             $localHandler->setItemSource(new LocalItemSource(array('item' => $rdfItem)));
             $itemAssetManager->loadAssetHandler($localHandler);
             $itemAssetManager->importAuxiliaryFiles($qtiItemResource)->importDependencyFiles($qtiItemResource, $dependencies);
             $itemAssetManager->finalize();
             $qtiModel = $this->createQtiItemModel($itemAssetManager->getItemContent(), false);
             $qtiService->saveDataItemToRdfItem($qtiModel, $rdfItem);
             // Finally, import metadata.
             $this->importResourceMetadata($metadataValues, $qtiItemResource, $rdfItem, $metadataInjectors);
             $eventManager = ServiceManager::getServiceManager()->get(EventManager::CONFIG_ID);
             $eventManager->trigger(new ItemImported($rdfItem, $qtiModel));
             $msg = __('The IMS QTI Item referenced as "%s" in the IMS Manifest file was successfully imported.', $qtiItemResource->getIdentifier());
             $report = common_report_Report::createSuccess($msg, $rdfItem);
         } catch (ParsingException $e) {
             $message = $e->getUserMessage();
             $report = new common_report_Report(common_report_Report::TYPE_ERROR, $message);
         } catch (ValidationException $ve) {
             $report = common_report_Report::createFailure(__('IMS QTI Item referenced as "%s" in the IMS Manifest file could not be imported.', $qtiItemResource->getIdentifier()));
             $report->add($ve->getReport());
         } catch (XmlStorageException $e) {
             $files = array();
             $message = __('There are errors in the following shared stimulus : ') . PHP_EOL;
             /** @var \LibXMLError $error */
             foreach ($e->getErrors() as $error) {
                 if (!in_array($error->file, $files)) {
                     $files[] = $error->file;
                     $message .= '- ' . basename($error->file) . ' :' . PHP_EOL;
                 }
                 $message .= $error->message . ' at line : ' . $error->line . PHP_EOL;
             }
             $report = new common_report_Report(common_report_Report::TYPE_ERROR, $message);
         } catch (PortableElementInvalidModelException $pe) {
             $report = common_report_Report::createFailure(__('IMS QTI Item referenced as "%s" contains a portable element and cannot be imported.', $qtiItemResource->getIdentifier()));
             $report->add($pe->getReport());
             if (isset($rdfItem) && !is_null($rdfItem) && $rdfItem->exists()) {
                 $rdfItem->delete();
             }
         } catch (PortableElementException $e) {
             // an error occured during a specific item
             if ($e instanceof common_exception_UserReadableException) {
                 $msg = __('Error on item %1$s : %2$s', $qtiItemResource->getIdentifier(), $e->getUserMessage());
             } else {
                 $msg = __('Error on item %s', $qtiItemResource->getIdentifier());
                 common_Logger::d($e->getMessage());
             }
             $report = new common_report_Report(common_report_Report::TYPE_ERROR, $msg);
             if (isset($rdfItem) && !is_null($rdfItem) && $rdfItem->exists()) {
                 $rdfItem->delete();
             }
         } catch (Exception $e) {
             // an error occured during a specific item
             $report = new common_report_Report(common_report_Report::TYPE_ERROR, __("An unknown error occured while importing the IMS QTI Package."));
             if (isset($rdfItem) && !is_null($rdfItem) && $rdfItem->exists()) {
                 $rdfItem->delete();
             }
             common_Logger::e($e->getMessage());
         }
     } catch (ValidationException $ve) {
         $validationReport = common_report_Report::createFailure("The IMS Manifest file could not be validated");
         $validationReport->add($ve->getReport());
         $report->setMessage(__("No Items could be imported from the given IMS QTI package."));
         $report->setType(common_report_Report::TYPE_ERROR);
         $report->add($validationReport);
     } catch (common_exception_UserReadableException $e) {
         $report = new common_report_Report(common_report_Report::TYPE_ERROR, __($e->getUserMessage()));
         $report->add($e);
     }
     return $report;
 }
 /**
  * Import the Test itself  by importing its QTI-XML definition into the system, after
  * the QTI Items composing the test were also imported.
  *
  * The $itemMapping argument makes the implementation of this method able to know
  * what are the items that were imported. The $itemMapping is an associative array
  * where keys are the assessmentItemRef's identifiers and the values are the core_kernel_classes_Resources of
  * the items that are now stored in the system.
  *
  * When this method returns false, it means that an error occured at the level of the content of the imported test
  * itself e.g. an item referenced by the test is not present in the content package. In this case, $report might
  * contain useful information to return to the client.
  *
  * @param core_kernel_classes_Resource $testResource A Test Resource the new content must be bind to.
  * @param XmlDocument $testDefinition An XmlAssessmentTestDocument object.
  * @param Resource $qtiResource The manifest resource describing the test to be imported.
  * @param array $itemMapping An associative array that represents the mapping between assessmentItemRef elements and the imported items.
  * @param string $extractionFolder The absolute path to the temporary folder containing the content of the imported IMS QTI Package Archive.
  * @param common_report_Report $report A Report object to be filled during the import.
  * @return Directory The newly created test content.
  * @throws taoQtiTest_models_classes_QtiTestServiceException If an unexpected runtime error occurs.
  */
 protected function importTestDefinition(core_kernel_classes_Resource $testResource, XmlDocument $testDefinition, Resource $qtiResource, array $itemMapping, $extractionFolder, common_report_Report $report)
 {
     foreach ($itemMapping as $itemRefId => $itemResource) {
         $itemRef = $testDefinition->getDocumentComponent()->getComponentByIdentifier($itemRefId);
         $itemRef->setHref($itemResource->getUri());
     }
     $oldFile = $this->getQtiTestFile($testResource);
     $oldFile->delete();
     $ds = DIRECTORY_SEPARATOR;
     $path = dirname($qtiResource->getFile()) . $ds . TAOQTITEST_FILENAME;
     $dir = $this->getQtiTestDir($testResource);
     $newFile = $dir->getFile($path);
     $newFile->write($testDefinition->saveToString());
     return $this->getQtiTestDir($testResource);
 }