getDataLanguage(); $basePath = $this->buildBasePath(); if (is_null($this->getItemModel())) { $report->setMessage($this->getExportErrorMessage(__('not a QTI item'))); $report->setType(\common_report_Report::TYPE_ERROR); return $report; } $dataFile = (string) $this->getItemModel()->getOnePropertyValue(new core_kernel_classes_Property(\taoItems_models_classes_ItemsService::TAO_ITEM_MODEL_DATAFILE_PROPERTY)); $resolver = new ItemMediaResolver($this->getItem(), $lang); $replacementList = []; $portableElements = $this->getPortableElementAssets($this->getItem(), $lang); $service = new PortableElementService(); $service->setServiceLocator(ServiceManager::getServiceManager()); $portableElementsToExport = []; $portableAssets = []; foreach ($portableElements as $element) { if (! $element instanceof Element) { continue; } try { $object = $service->getPortableObjectFromInstance($element); } catch (PortableElementException $e) { $message = __('Fail to export item') . ' (' . $this->getItem()->getLabel() . '): ' . $e->getMessage(); return \common_report_Report::createFailure($message); } $portableElementExporter = $object->getModel()->getExporter($object, $this); $portableElementsToExport[$element->getTypeIdentifier()] = $portableElementExporter; try { $portableAssets = array_merge($portableAssets, $portableElementExporter->copyAssetFiles($replacementList)); } catch (\tao_models_classes_FileNotFoundException $e) { \common_Logger::i($e->getMessage()); $report->setMessage('Missing portable element asset for "' . $object->getTypeIdentifier() . '"": ' . $e->getMessage()); $report->setType(\common_report_Report::TYPE_ERROR); } } $assets = $this->getAssets($this->getItem(), $lang); foreach ($assets as $assetUrl) { try { $mediaAsset = $resolver->resolve($assetUrl); $mediaSource = $mediaAsset->getMediaSource(); $mediaIdentifier = $mediaAsset->getMediaIdentifier(); if (!$mediaSource instanceof HttpSource && !Base64::isEncodedImage($mediaIdentifier)) { $link = $mediaIdentifier; if ($mediaSource instanceof ProcessedFileStreamAware) { $stream = $mediaSource->getProcessedFileStream($link); } else { $stream = $mediaSource->getFileStream($link); } $baseName = ($mediaSource instanceof LocalItemSource) ? $link : 'assets/' . $mediaSource->getBaseName($link); $replacement = $this->copyAssetFile($stream, $basePath, $baseName, $replacementList); $replacementList[$assetUrl] = $replacement; } } catch (\tao_models_classes_FileNotFoundException $e) { $replacementList[$assetUrl] = ''; $report->setMessage('Missing resource for ' . $assetUrl); $report->setType(\common_report_Report::TYPE_ERROR); } } try { $xml = Service::singleton()->getXmlByRdfItem($this->getItem()); } catch (FileNotFoundException $e) { $report->setMessage($this->getExportErrorMessage(__('cannot find QTI XML'))); $report->setType(\common_report_Report::TYPE_ERROR); return $report; } $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; if ($dom->loadXML($xml) === true) { $xpath = new \DOMXPath($dom); $attributeNodes = $xpath->query('//@*'); $portableEntryNodes = $xpath->query("//*[local-name()='entry']|//*[local-name()='property']") ?: []; unset($xpath); foreach ($attributeNodes as $node) { if (isset($replacementList[$node->value])) { $node->value = $replacementList[$node->value]; } } foreach ($portableEntryNodes as $node) { $node->nodeValue = strtr(htmlentities($node->nodeValue, ENT_XML1), $replacementList); } foreach ($portableElementsToExport as $portableElementExporter) { $portableElementExporter->exportDom($dom); } } else { $report->setMessage($this->getExportErrorMessage(__('cannot load QTI XML'))); $report->setType(\common_report_Report::TYPE_ERROR); return $report; } if (($content = $dom->saveXML()) === false) { $report->setMessage($this->getExportErrorMessage(__('invalid QTI XML'))); $report->setType(\common_report_Report::TYPE_ERROR); } // Possibility to delegate (if necessary) some item content post-processing to sub-classes. $content = $this->itemContentPostProcessing($content); // add xml file $this->getZip()->addFromString($basePath . '/' . $dataFile, $content); if (! $report->getMessage()) { $report->setMessage(__('Item "%s" is ready to be exported', $this->getItem()->getLabel())); } ///return some useful data to the export report $report->setData(['portableAssets' => $portableAssets]); return $report; } /** * Format a consistent error reporting message * * @param string $errorMessage * @return string */ private function getExportErrorMessage($errorMessage) { return __('Item "%s" cannot be exported: %s', $this->getItem()->getLabel(), $errorMessage); } public function copyAssetFile(StreamInterface $stream, $basePath, $baseName, &$replacementList) { $replacement = $baseName; $count = 0; while (in_array($replacement, $replacementList)) { $dot = strrpos($baseName, '.'); $replacement = $dot !== false ? substr($baseName, 0, $dot) . '_' . $count . substr($baseName, $dot) : $baseName . $count; $count++; } // To check if replacement is to replace basename ??? // Please check it seriously next time! $newRelPath = (empty($basePath) ? '' : $basePath . '/' ) . preg_replace('/^(.\/)/', '', $replacement); $this->addFile($stream, $newRelPath); $stream->close(); return $replacement; } /** * Get the item's assets * * @param \core_kernel_classes_Resource $item The item * @param string $lang The item lang * * @return string[] The assets URLs */ protected function getAssets(\core_kernel_classes_Resource $item, $lang) { $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang); if (is_null($qtiItem)) { return []; } $assetParser = new AssetParser($qtiItem, $this->getStorageDirectory($item, $lang)); $assetParser->setGetSharedLibraries(false); $returnValue = []; foreach ($assetParser->extract() as $type => $assets) { foreach ($assets as $assetUrl) { foreach (self::$BLACKLIST as $blacklist) { if (preg_match($blacklist, $assetUrl) === 1) { continue(2); } } $returnValue[] = $assetUrl; } } return $returnValue; } protected function getPortableElementAssets(\core_kernel_classes_Resource $item, $lang) { $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang); if (is_null($qtiItem)) { return []; } $directory = $this->getStorageDirectory($item, $lang); $assetParser = new AssetParser($qtiItem, $directory); $assetParser->setGetCustomElementDefinition(true); return $assetParser->extractPortableAssetElements(); } /** * Get the item's directory * * @param \core_kernel_classes_Resource $item The item * @param string $lang The item lang * * @return Directory The directory */ protected function getStorageDirectory(\core_kernel_classes_Resource $item, $lang) { $itemService = \taoItems_models_classes_ItemsService::singleton(); $directory = $itemService->getItemDirectory($item, $lang); //we should use be language unaware for storage manipulation $path = str_replace($lang, '', $directory->getPrefix()); return $itemService->getDefaultItemDirectory()->getDirectory($path); } protected function getServiceManager() { return ServiceManager::getServiceManager(); } /** * Get the service to export Metadata * * @return MetadataExporter */ protected function getMetadataExporter() { if (! $this->metadataExporter) { $this->metadataExporter = $this->getServiceManager()->get(MetadataService::SERVICE_ID)->getExporter(); } return $this->metadataExporter; } }