/** * Starts the import based on the form * * @param \core_kernel_classes_Class $class * @param \tao_helpers_form_Form $form * @return \common_report_Report $report */ public function import($class, $form) { //as upload may be called multiple times, we remove the session lock as soon as possible session_write_close(); try { $file = $form->getValue('source'); $service = MediaService::singleton(); $classUri = $class->getUri(); if (is_null($this->instanceUri) || $this->instanceUri === $classUri) { //if the file is a zip do a zip import if ($file['type'] !== 'application/zip') { try { self::isValidSharedStimulus($file['uploaded_file']); $filepath = $file['uploaded_file']; $name = $file['name']; if (!$service->createMediaInstance($filepath, $classUri, \tao_helpers_Uri::decode($form->getValue('lang')), $name, 'application/qti+xml')) { $report = \common_report_Report::createFailure(__('Fail to import Shared Stimulus')); } else { $report = \common_report_Report::createSuccess(__('Shared Stimulus imported successfully')); } } catch (XmlStorageException $e) { // The shared stimulus is not qti compliant, display error $report = \common_report_Report::createFailure($e->getMessage()); } } else { $report = $this->zipImporter->import($class, $form); } } else { if ($file['type'] !== 'application/zip') { self::isValidSharedStimulus($file['uploaded_file']); $filepath = $file['uploaded_file']; if (in_array($file['type'], array('application/xml', 'text/xml'))) { $name = basename($file['name'], 'xml'); $name .= 'xhtml'; $filepath = dirname($file['name']) . '/' . $name; \tao_helpers_File::copy($file['uploaded_file'], $filepath); } if (!$service->editMediaInstance($filepath, $this->instanceUri, \tao_helpers_Uri::decode($form->getValue('lang')))) { $report = \common_report_Report::createFailure(__('Fail to edit shared stimulus')); } else { $report = \common_report_Report::createSuccess(__('Shared Stimulus edited successfully')); } } else { $report = $this->zipImporter->edit(new \core_kernel_classes_Resource($this->instanceUri), $form); } } return $report; } catch (\Exception $e) { $report = \common_report_Report::createFailure($e->getMessage()); return $report; } }
/** * Handle the process to manage shared stimulus files * * @param $absolutePath * @param $relativePath * @return array * @throws \qtism\data\storage\xml\XmlStorageException */ public function handle($absolutePath, $relativePath) { $sharedFiles = $this->getSharedFiles(); $md5 = md5_file($absolutePath); if (isset($sharedFiles[$md5])) { \common_Logger::i('Auxiliary file \'' . $absolutePath . '\' linked to shared storage.'); return $sharedFiles[$md5]; } SharedStimulusImporter::isValidSharedStimulus($absolutePath); $newXmlFile = SharedStimulusPackageImporter::embedAssets($absolutePath); $itemContent = $this->sharedStorage->add($newXmlFile, basename($relativePath), $this->parentPath); if (method_exists($this->sharedStorage, 'forceMimeType')) { $asset = $this->itemSource->resolve($itemContent['uri']); $this->sharedStorage->forceMimeType($asset->getMediaIdentifier(), 'application/qti+xml'); } $this->addSharedFile($md5, $itemContent); \common_Logger::i('Auxiliary file \'' . $absolutePath . '\' added to shared storage.'); return $itemContent; }
/** * @dataProvider sharedStimulusConvertProvider */ public function testEmbedAssets($directory, $exception, $converted) { $xmlDocument = new XmlDocument(); $xmlDocument->load($directory . '/stimulus.xml'); try { $xmlConverted = SharedStimulusPackageImporter::embedAssets($directory . '/stimulus.xml'); $xmlDocument->load($xmlConverted); $strXml = $xmlDocument->saveToString(); $xmlDocument->load($converted); $convertStr = $xmlDocument->saveToString(); $this->assertEquals($convertStr, $strXml, __('Conversion return a wrong string')); } catch (\tao_models_classes_FileNotFoundException $e) { $this->assertNotNull($exception, __('It should not throw an exception')); if (!is_null($e)) { $this->assertInstanceOf(get_class($exception), $e, __('The exception class is wrong')); if ($exception->getMessage() !== '') { $this->assertEquals($exception->getMessage(), $e->getMessage(), __('The exception message is wrong')); } } } }
/** * @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; }